Setting up Models and working with them

See also:

Some advice on how to setup models, layout the files, and use them in your application. I hope this saves you some time in figuring out how the Model and Record classes interact and can make coding a little easier. First thing to keep in mind is that a Model describes a Table in your database, while a Record is used to describe a single row in your database.

For this example, I'll describe a typical blogging application where an author can write one or more Posts that are related to one or more Tags (Categories)

File Layout

In a typical Solar layout your application lives in your Vendor_App directory. Similarly, your Model and Record classes will live in your Vendor_Model directory, in part to keep class names and path manageably short. For our minimal blog app, the file system hierarchy will look like the following:

  • Vendor/
    • App/
    • Model/
      • Posts.php
      • Posts/
        • Record.php
      • Tags.php
      • Tags/
        • Record.php

Classes in the files above have the following declarations:

Model/Posts.php

class Vendor_Model_Posts extends Solar_Model {
}

Model/Tags.php

class Vendor_Model_Tags extends Solar_Model {
}

Model/Posts/Record.php

class Vendor_Model_Posts_Record extends Solar_Model_Record {
}

Model/Tags/Record.php

class Vendor_Model_Tags_Record extends Solar_Model_Record {
}

The file system layout above allows you to query your database tables using a Vendor_Model_Posts object which returns a collection of Vendor_Model_Posts_Record objects. We'll get a taste of what this can do a bit later.

Model describes your table

As we mentioned at the top, your model class describes a table in your databases so that solar can work with it. This table metadata is defined in the _setup method. For our blogging application, the Post table could be described as follows:

class Vendor_Model_Posts extends Solar_Model {
    /**
     *
     * Model setup for the blog post table.
     *
     * @return void
     *
     */
    protected function _setup() {
        /* The posts table */
        $this->_table_name = 'posts';
 
        /* The columns in our posts table */
        $this->_table_cols = array(
 
            'id' => array(
                'type'    => 'int',
                'require' => true,
                'primary' => true,
                'autoinc' => true,
            ),
 
            'created' => 'timestamp', //datetime in mysql
 
            'modified' => 'timestamp',//datetime in mysql
 
            'name' => array(
                'type'    => 'varchar',
                'size'    => 255,
                'require' => true,
            ),
 
            'content' => 'clob',
        );
    }
}

Once your model is defined, you can use Solar's SQL magic to query for data. For example, to retrieve the last five posts you would write:

    /* this is a code fragment */
    $posts  = new Vendor_Model_Posts();
    $latest = $posts->fetchAll(array(
        'order'  => array ('created DESC'),
        'paging' => 5,
        'page'   => 1
    ));

Records can add functionality to your table rows

Now that we have described our table, we can pretty easily loop through and work with rows from our table with Record objects. Records can add some intelligence to your table rows. Let's say you want to calculate a URL friendly “slug” from your post's title field. The slug is constructed by lower-casing the title, striping punctuation, and changing spaces to underscores. Since you might want to use this slug in a variety of scripts, it'd be nice to have the functionality in one place instead of copying and pasting the code across multiple files. To cook up this bit of magic, first add the following line to your Post model's _setup() method:

    $this->_calculate_cols = array('slug');

Also, in your Vendor_Model_Posts_Record model, add a method named `_ _getSlug()`.

class Vendor_Model_Posts_Record extends Solar_Model_Record {
    private $_slug;
    public function __getSlug()
    {
        // calculate once
        if (!isset($this->_slug)) {
            $slug = strtolower($this->title);
            $slug = preg_replace('/[^A-Za-z0-9\s]/', '', $slug); // strip non-numeric, or spaces
            $slug = preg_replace('/(\s\s+)/', ' ', $slug); // multiple spaces to single space
            $this->_slug = str_replace(' ', '_', $slug); // spaces to underscores
        }
        return $this->_slug;
}

Under the hood, the Solar_Sql_Model_Record class, via the magic of PHP5's `_ _get()` function, will look for `_ _getSlug()` when you try to access `$record→slug`. To complete this example, we could print out the slugs for our 5 latest posts from above with the following fragment:

    foreach ($latest as $post) {
        echo $post->slug . '<br>';
    }

Models are social too

Tables are seldom self-contained, in our case a tag may belong to one or more posts. The relationship between a Post and a Tag would be stored in a posts_tags table with at least two columns - a post_id and a tag_id. Solar's Model class can relieve you of writing the joins to fetch the Tags that belong to a Record if you describe the relationship in the Vendor_Model_Post class. Add the following lines to your _setup method:

    $this->_hasMany('posts_tags', array(
        'foreign_class' => 'Vendor_Model_PostsTags', // the full name of your Solar model
        'foreign_key' => 'post_id', // the column that has this Model's primary key
    );
 
    $this->_hasMany('tags', array(
         'foreign_class' => 'Vendor_Model_Tags',
         'through'       => 'posts_tags', // this is the name we defined in the hasMany above
         'through_key'   => 'tag_id', // the column that has the Tag record's primary key, in the news_tags tables
    );

If you're following along, you'll notice that you have to add another model to describe the table that joins a Post to its related Tags.

Solar::autoload('Solar_Model');
class Vendor_Model_PostsTags extends Solar_Model
{
    /**
     *
     * Model setup.
     *
     * @return void
     *
     */
    protected function _setup()
    {
        /**
         * Table name, columns, and indexes.
         */
        $this->_table_name = 'posts_tags';
 
        $this->_table_cols = array(
            'post_id' => array(
                'type'    => 'int',
                'require' => true,
            ),
 
            'tag_id' => array(
                'type'    => 'int',
                'require' => true,
            ),
        );
 
        $this->_index = array(
            array('post_id', 'tag_id')
        );
 
 
        $this->_belongsTo('post', array(
            'foreign_class' => 'Vendor_Model_Posts',
            'foreign_key'   => 'post_id',
        ));
 
        $this->_belongsTo('tag', array(
            'foreign_class' => 'Vendor_Model_Tags',
            'foreign_key'   => 'tag_id',
        ));
    }
}

Now that Solar knows how Tags are related to a Post, you can access the tags with:

   // assume $post is already an instance of Vendor_Model_Posts_Record
   $my_tags = $post->tags;
   foreach ($my_tags as $tag} {
       echo $tag->name . '<br />';
   }

Magic fetch methods

You can call “fetchAllByStatus(1)” and the method doesn't necessarily need to exist in your model. When called, in this example it'd return all records with the column “status” = 1. It is also available for fetchOne and you can define *two* column values. Some examples:

    $record = $model->fetchOneByStatus(1);
    $record = $model->fetchOneByStatusAndName(1, 'foo');
 
    $collection = $model->fetchAllByName('foo');
    $collection = $model->fetchAllByNameAndOtherName('foo', 'bar');

An optional array with parameters for the fetch (like those we use for normal fetch's) can be added as a last parameters for all the examples above:

    $record = $model->fetchOneByStatus(1, $params);
    $record = $model->fetchOneByStatusAndName(1, 'foo', $params);
 
    ... etc.

These are very handy for common/basic/quick fetch's we do all the time.

 
tutorials/setting_up_models.txt · Last modified: 2008/08/25 08:48 (external edit)