Database

Contents...

Database manipulation classes represent the "Model" layer as defined in MVC. Webasyst framework contains abstract class waModel for the implementation of database models. Description classes for entities used in applications must extend class waModel. Model class files must be located in subdirectory lib/models/ inside the main application directory.

A model description must contain only one required field: the application's table name as a value of the  $table variable. Example of a model description file:

<?php

class blogModel extends waModel
{
    protected $table = 'blog';
}

A model is initialized using the syntax shown below:

$model = new blogModel();

In the above sample blog is an example of an application identifier (APP_ID). After initialization, a model retrieves the description of the specified database table (using an SQL query of the form DESCRIBE $table) and saves it in cache. At this step the table's primary key as well as table fields and their types are defined.

In the source code of controllers and actions any interaction with the database is carried out using only application model classes:

// creating a model instance to retrieve data from the database
$model = new guestbookModel();
// retrieving guestbook records from the database $records = $model->order('datetime DESC')->fetchAll();

Class waModel contains several methods for automatic generation of SQL queries which allow the source code of an application to be independent of the specific kind of a DBMS used on the server.

SQL query generation methods

SELECT queries

// Retrieve a record by its primary key value
$model->getById($id);

// Retrieve a record by of one of its fields' values
$model->getByField('name', $name);  // returns the first found record

// If all records matching the search parameters should be returned like in the example above,
// then the third argument should be added: true
$model->getByField('name', $name, true);

// Several fields' values can be specified
// they will be combined in the generated SQL query using AND operator
$model->getByField(array('name' => $name, 'contact_id' => $contact_id));

// If it is necessary to select records by several values of a single field,
// then an array should be specified instead of a variable, in this case the resulting SQL query
// will contain IN operator
$model->getByField('name' , array($name1, $name2));

// Aggregate queries of the form SELECT COUNT(*) ... WHERE ... are generated by method countByField
// Calling arguments are the same as for getByField
$model->countByField($field, $value);
$model->countByField(array('name' => $name, 'contact_id' => $contact_id));
$model->countByField('name' , array($name1, $name2));

UPDATE queries

// Update a record by the primary key value
$model->updateById($id, $data); // $data is an associative array of the values to be updated
$model->updateById($id, array('field_1' => $field_1, 'field_2' => $field_2));

// Method updateByField is similar to getByField,
// it accepts an array of the values to be updated as the third argument
$model->updateByField('contact_id', $contact_id, array('published' => true));

DELETE queries

// There are the following two DELETE methods similar to those for UPDATE queries:
$model->deleteById($id);
$model->deleteByField($field, $value);

INSERT and REPLACE queries

// Both methods affect only one record
$model->insert($data); // if auto_increment is enabled, the method will return the id of the inserted record $model->replace($data);

Execution of directly written SQL queries

The framework has two basic methods for executing SQL queries: query and exec. As the first argument they accept an SQL query, and an optional array of values for placeholders as the second argument.

Example:

$model->query("SELECT * FROM ".$this->table." WHERE id = i:id", array('id' => $id));

Notes:

Available data types for placeholders:

If no data type has been specified for a placeholder (e.g., if you use only :id in an SQL query, then the String type is used by default. Use of placeholders makes the creating of SQL queries easier and, which is far more important, eliminates the possibility of writing code prone to SQL injection vulnerabilities.

Using placeholders is not a requirements; however, if they are not used, a developer is responsible for correct use of data in an SQL query. In this case model method escape may be useful, which escapes text strings (if DBMS MySQL is used, it works exactly like PHP function mysql_real_escape_string):

$model->query("SELECT * FROM ".$this->table." WHERE id = ".(int)$id);
$model->query("SELECT * FROM ".$this->table." WHERE name LIKE '".$this->escape($name)."'";

The only difference between methods query and exec is in the result which they return.

Method query($sql, $params = null)

This method executes an SQL query and returns the result object. The type of the returned object depends on the query type:

SELECT queries

An object of type waDbResultSelect is returned, which can be used in a foreach cycle, for instance. Examples of working with waDbResultSelect:

$result = $model->query("SELECT * FROM ".$this->table." WHERE text LIKE '%test%'");

// Retrieve the number of items in the result (similar to function mysql_num_rows)
$result->count();

// Access each returned item in the result:
foreach ($result as $row) {
    ...
}
// Retrieve all result items as an array
$data = $result->fetchAll();

// If a field name is specified as the first argument for method fetchAll,
// then an associative array is returned with the specified field's values as array keys
$data = $result->fetchAll('id');

// If the result is expected to return only one value or if only one returned value is necessary,
// the following methods will be useful:
// similar to mysql_fetch_assoc $result->fetch();
// executes fetch and returns a value by the specified key // or the first array item's value, if no key is specified $result->fetchField('id');
// here is how you can retrieve the result of a COUNT query $n = $model->query("SELECT count(*) FROM ".$this->table." WHERE ...")->fetchField();

DELETE and UPDATE queries

A result object of type waDbResultDelete or waDbResultUpdate respectively is returned.

$result = $model->query("UPDATE ".$this->table." SET name = 'no name' WHERE name = ''");
// Retrieve the number of affected table records (mysql_affected_rows function) $result->affectedRows()

INSERT queries

An object of type waDbResultInsert is returned.

// Retrieve the value of the primary key of the created record
// if auto_increment is enabled (mysql_insert_id function)
$result->lastInsertId()

Method exec($sql, $params = null)

This method executes an SQL query and returns the result received from the DBMS adapter (in case of MySQL the result of the mysql_query function is returned). Using this method is convenient when no result is required or when it is necessary to verify the fact of a query execution.

SELECT queries constructor

SELECT queries can be generated using a simple constructor as shown below:

// Select all records sorted by field datetime:
$records = $model->order('datetime DESC')->fetchAll();
// Retrieve an associative array id => name $data = $model->select('id,name')->fetchAll('id', true); // The current version offers methods select, where, order: $model->select('id, name, datetime') ->where('contact_id = '.(int)$contact_id) ->order('datetime DESC') ->fetchAll('id'); // The previous example is equal to the following: $model->query("SELECT id, name, datetime WHERE contact_id = '.(int)$contact_id.' ORDER BY datetime DESC')->fetchAll('id');

Recommendations on implementation of custom model methods in an application

In order to avoid repeating equal parts of code (using standard model methods) in several PHP files, you can create custom methods for a model to implement frequently executed actions.

Example:

// Method returning all contact records
public function getByContact($contact_id)
{
	return $this->getByField('contact_id', $contact_id, true);
}

All methods used for manipulation with each database table are implemented in the corresponding model class. Existing model methods can later be enhanced (e.g., through adding of caching functions).

We recommend implementing most operations on a certain database table in a model's methods not associated with table fields' names. This will, in case of code refactoring, allow applying changes only to one source file: that of the model class.

Connections to other databases

Class waModel can also be used to establish connections to other databases in addition to the one containing Webasyst data. This may be useful, for example, to import data from third-party software.

To establish a connection to another database, you need to specify connection credentials when creating an instance of a model class in your application. There are several ways to do so as described below in this section.

Creating permanent connection configuration

If you are planning to use connection to a certain database multiple times, add the appropriate credentials to framework configuration file wa-config/db.php as an associative array:

<?php


return array (
  'default' => 
      array (
          'host' => 'localhost',
          'user' => 'user1',
          'password' => '12345678',
          'database' => 'webasyst',
          'type' => 'mysqli',
      ),


  'extra' => 
      array (
          'host' => 'localhost',
          'user' => 'user2,
          'password' => '87654321',
          'database' => 'wordpress',
          'type' => 'mysqli',
      ),


);

The above example shows how a new record named extra is added to the configuration file. To get connected to the database named wordpress (in this example), you need to create a model instance in your app's PHP code as shown below:

$extra_model = new waModel('extra');

Calling the standard methods of such a model will query only the database whose name is contained in the specified database configuration.

Setting connection credentials dynamically

Should your application need to connect to different databases, you may want to specify the connection credentials dynamically, as an associative array. Below is provided an example of this approach:

$extra_connection = array (
    'host' => 'localhost',
    'user' => 'user2',
    'password' => '87654321',
    'database' => 'somedb',
    'type' => 'mysqli',
);

$extra_model = new waModel($extra_connection);

Both methods described in this section are applicable both to the basic class waModel and to custom model classes which you may create (e.g., myappSomeModel):

$extra_model = new myappSomeModel('extra');

or

//array $extra_connection must contain valid connection credentials

$extra_model = new myappSomeModel($extra_connection);

When using connections to other databases in your application, always be aware of the specific table structure of those databases when designing custom methods for your models.

To use connections to databases with different table structure, it is advisable to create a separate model class for each type of database. This will make your application code more transparent and will facilitate the debugging.