Make great applications with PHP
 

simply put: Classes and Models - Generating models, and autoloading classes at runtime.


Classes

Using PHP's autoload mechanism, PIE can 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 PIE will autoload them when you need. This is one way that PIE 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 impleented using handlers, the M part is implemented using classes.

In PIE, a model is a class that represents a persistent object, such as a database row (or table), or contains functionality to manipulate persistent objects. You can actually generate models automatically from a schema, simply by calling $db->generateModels($conn_name). Here is a script that you can drop into your scripts folder, to re-generate models from a database connection named "something":

<?php // Let's say this file is APP_DIR/scripts/$module/something.php

include(dirname(__FILE__).'/../pie.inc.php');
Db::connect('something')->generateModels(APP_DIR.DS.'classes');

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

PIE comes with a built-in Db_Row class, which contains common functionality that models have. In fact, the models that PIE 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 = Db::connect('youMixer')->select('*', 'mix')->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->first_name = "Gregory";
$user->last_name = "Magarshak";
$user->save();                    // inserts into the database.
$user_id = $user->id;             // if there was an autoincrementing id, it was now set

// Retrieving rows:

$user = new User_User();
$user->first_name = 'Gregory';
$user = $user->retrieve('*');
	// sql: SELECT * FROM myDatabase.myPrefix_user WHERE first_name = "Gregory"
if (!$user) {
	throw new Exception("No such user");
}
echo $user->first_name; // the user row has been retrieved

// Retrieving a bunch of rows:

$rows = Users_User::select('*')->where('id = 4')->fetchDbRows(); 
	// sql: 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.

$user = new User_User();
$user->first_name = 'Gregory';
$user->retrieve();                  // (1) was a row found and retrieved?
$user->last_name = 'Magarshak';     // updates the object inside our PHP script
$user->save();                      // issues an INSERT or UPDATE query, depending on (1).

// Deleting rows

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

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

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

// However, you should do it in a single transaction, like so:

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

Besides this, Db_Row has a lot more functionality. You should its other functions in the class reference.

What is autogenerated

When you autogenerate models from a particular database, PIE 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 "time_created" and "time_updated" (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 PIE'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 PIE 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.by_user_id' => 'u.id'));
		
		$this->hasMany('tags', array(
			'a' => 'Items_Article',
			't' => 'Items_Tag'              // returns Items_Category objects
		), array('a.item_id' => 't.item_id'));
		
		$this->hasMany('tagVotes', 
			array(
				'a' => 'Items_Article',
				'u' => 'Users_User',
				'tv' => 'Items_TagVote',    // returns Items_Tag objects
			),
			array('a.item_id' => 'tv.item_id'),
			array('u.id' => 'tv.by_user_id')
		);
	}
}

You can then retrieve related objects by doing things like:

$article->get_tags('*');
$article->get_tagVotes('*');
$article->get_author('u.first_name, u.last_name');

Caching

As we saw in the database article, PIE does caching at the level of query execution by default (i.e. same sql only hits the database once). However, PIE 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:

	$tags = $article->get_articles(); // get related tags
	$article->set('tags', $articles); // save the result in the cache
	
	// sometime later:
	// check the cache -- if not there, then retrieve tags
	$tags = $article->get('tags', $article->get_tags()); 

Complete reference to PHP ON PIE

TODO: include an iframe with PHPDoc-generated reference