• log out

Classes

Using PHP's autoload mechanism, Qbix can locate files where classes are defined, and load them when you first invoke them in your code. For a class named Foo_Bar_Baz it will try to load the classes/Foo/Bar/Baz.php file. This convention is similar to PEAR and the Zend Framework. In fact, you can take classes from both of those and simply drop them into the classes folder, and Qbix will autoload them when you need. This is one way that Qbix lets you use lots of cool classes from other libraries and frameworks without reinventing the wheel.

Generating Models

We have spent a lot of time talking about views and controllers in other articles in the guide. In this one, we will focus on the M part of MVC - the models. Whereas the C part is usually implemented using handlers, the M part is implemented using classes.

In Qbix, a model is a class that represents a persistent object, such as a database row (or table), or contains functionality to manipulate these objects. You can actually generate models automatically from existing tables after you have set up a database connection in the app's config, simply by running the script APP_DIR/scripts/Q/models.php

Yes, it's as simple as that. You can see the files generated for the models in the classes folder. One of them is named after the database connection, and has methods like Something::db(). The others are prefixed with Something_, and represent the tables in the database.

Once the files for the models are generated, you can edit them. Don't edit the ones inside the classes/Base/ folder, since your changes will be overwritten the next time you decide to re-generate the schema. However, you are free to edit the non-base model classes, and you can implement any methods you wish in your models by adding them to these classes.

The Db_Row class

Qbix comes with a built-in Db_Row class, which contains common functionality that models have. In fact, the models that Qbix autogenerates for you all extend this class. Out of the box, Db_Row is a full ORM (object-relational mapper) that implements the ActiveRecord pattern. In addition to PDO methods like fetchAll, you can fetch and fill Db_Row objects, like so:

$rows = YouMixer_Mix::select('*')
          ->fetchDbRows();

Although you can create instances of Db_Row directly, you are not expected to do that. Instead, Db_Row is meant to be extended by a class implementing a model. For example, an autogenerated model class like Users_User would extend Base_Users_User, which in turn extends Db_Row, thereby inheriting all of its methods. Here are some examples of how you canuse them:

// Inserting new rows:

$user = new Users_User();
$user->username = "SomeoneCool";
$user->save(); // insert into db
$user_id = $user->id; // id was autogenerated

// Retrieving rows:

$user = new User_User();
$user->username = 'Gregory';
$user = $user->retrieve();
// SELECT * FROM myDatabase.myPrefix_user
// WHERE username = "Gregory"

if (!$user) {
  throw new Exception("No such user");
}
// the user row has been retrieved
echo $user->username;

// Retrieving a bunch of rows:

$rows = Users_User::select('*')
        ->where('id = 4')
        ->fetchDbRows();
// SELECT * FROM myDatabase.myPrefix_user
// WHERE id = 4

// Updating rows one at a time
// For mass updates, skip the ORM and
// use Users_User::update() instead.

$r = $user->retrieve();
$user->content = 'Gregory';
$user->save();
// issues an INSERT or UPDATE query,
// depending on $r

// Deleting rows

$user = new Users_User();
$user->id = 4;
$user->remove();

// Often, you would have this sort of pattern:

$user = new Users_User();
$user->id = '19c8f7';
if (!$user->retrieve()) {
  $user->save(); // insert it, if not there
}
$user_id = $user->id; // either way, it's set

// You can do it in a more atomic way:

$user = new Users_User();
$user->id = '19c8f7';
$user->save(true);
// On MySQL, it uses a special clause
// called ON DUPLICATE KEY UPDATE

Every Db_Row object also includes a Q_Tree, which means it supports the following methods, used to associate additional app data with the row, without saving it to the database:

$fields = $row->getAll();
$foo = $row->get('foo', 'bar', $default);
$row->set('foo', 'bar', $value);
$row->clear('foo', 'bar')

Besides this, Db_Row has a lot more functionality, which you can discover in the class reference.

Exporting Data to the Client

Often, when generating a page with PHP, you may want to output some data from Db_Row objects to the client JS environment. Here is how you would typically do that:

Q_Response::setScriptData(
  "First.someRow", 
  $row->exportArray($options)
);

Q_Response::setScriptData(
  "First.manyRows", 
  Db::exportArray($rows, $options)
);

Each Model class can override the exportArray($options) method to return an array that is safe to send to the client.

What is Autogenerated

When you autogenerate models from a particular database, Qbix creates classes named something like Base_ConnName_TableName. These classes already do a lot of things for you, including

  • Information — The model overrides the setUp method of Db_Row and specifies the names of the table and database connection, as well as the fields of the primary key for the table. This information is used by Db_Row when it generates queries to execute.
  • Validation — Before a value is assigned to a specific field, the model checks that it fits the type of the field (column) as it is described in the schema.
  • Enumeration — You can get a list of all the field names by calling ::fieldNames().
  • Magic fields — If your table contains fields called "insertedTime" and "updatedTime" (of type datetime), they are filled with the right value when you call $row->save().
  • Helper methods — When you call methods such as Users_User::select(), Users_User::update() and so on, a query is returned that automatically fills in your database and table name, so you can just add some clauses (such as ->where(...)) and execute it. In addition, the Db_Query will know what kind of classes to return when you call ->fetchDbRows(). To illustrate:
    $users = Users_User::select('*')
      ->where(array(
        'name LIKE ' => $pattern
      ))->fetchDbRows();
    	
    // Now, $users is an array of
    // zero or more Users_User objects.
    

Relations

One of the things you will want to add to your models is the relationships between your tables. You can, of course, write your own methods, such as $user->getArticles(). However, you can also tell Qbix's database library about these relationships, and have it generate the code for you.

The place to set this up is your model's setUp() method, where you can fill in your own code. Here, you would use the $this->hasOne(...) $this->hasMany(...) methods to tell Qbix about the relationships between your models. Below is an example:

class Items_Article extends Base_Items_Article
{
  function setUp()
  {
    parent::setUp();
    
    $this->hasOne('author', array(
      'a' => 'Items_Article',
      'u' => 'Users_User'             
      // returns Users_User objects
    ), array('a.byUserId' => 'u.id'));
    
    $this->hasMany('tags', array(
      'a' => 'Items_Article',
      't' => 'Items_Tag'              
      // returns Items_Tag objects
    ), array('a.itemId' => 't.itemId'));
    
    $this->hasMany('tagVotes', 
      array(
        'a' => 'Items_Article',
        'u' => 'Users_User',
        'tv' => 'Items_TagVote',    
        // returns Items_TagVote objects
      ),
      array('a.itemId' => 'tv.itemId'),
      array('u.id' => 'tv.byUserId')
    );
  }
}

You can then retrieve related objects by doing things like:

$article->get_tags('*');
$article->get_tagVotes('*', array(
  'u' => $user // get votes placed by user
));
$article->get_author('u.firstName, u.lastName');

Caching

As we saw in the database article, Qbix does caching at the level of query execution by default (i.e. same sql only hits the database once). However, Qbix also encourages is caching at the level of models. To help you do this, each Db_Row object supports the methods get, set and clear, to let you set arbitrary data on the rows, which is used only in the script and isn't saved back to the database:

// get related tags
$tags = $art->get_tags();

// save the result in the cache
$art->set('tags', $tags);

// sometime later:
// check the cache -- if not there,
// then retrieve tags
$tags = $art->get('tags', $art->get_tags());