Models

Models make up the data access layer of Maveric. Maveric implements ORM and the Active Record design pattern, and is heavily influenced by the Ruby on Rails implementation.

Models should extend the Model class to take advantage of these built-in features. Each model should correspond to a database table, and each instance of that model will correspond to a row in that table.

Basic Example

Let's say we have a table called 'users', with the following structure:

idusernamepassword

All we need to do to create our User model is create a file called User.php in the models/ directory, and create a class that extends Model:

<?php
/**
 * User model
 * models/User.php
 */
class User extends Model {}

That's it! Let's populate our table with some dummy data:

idusernamepassword
1johnhashed1
2jameshashed2

Now let's see how easy it is to access a row in the table:

<?php
/**
 * Somewhere in a Controller
 */

$user = new User(1);

echo $user->username; // echos "john"

Creating Tables

Every table with a corresponding model must have a primary, auto-increment key named id. There is special handling for columns named created and updated and there are general rules for foreign key names, but other than that, any other columns you create will simply be accessible as object properties.

Tables should generally be the plural form of whatever data they store. For example, a table of users would be users. A table storing order data would probably be called orders. If you need to deviate from this convention, you can explicitly set the name for the table like so:

<?php
/**
 * User model
 * models/User.php
 */
class User extends Model
{
  protected $table = 'userdata'; // put the table name here
}

id

The primary key of each table should be named id and should be an auto-incrementing integer. In MySQL, the column definition would be

id INT NOT NULL AUTO_INCREMENT PRIMARY KEY

A BIGINT or other long integer type could also be used, but PHP's handling of large integers is limited.

created & updated

Two fields with special handling are created and updated, which hold Unix timestamps. Modifying the values of these fields directly will not work, since they are set when the save() method is called.

Foreign Keys

See the "Relationships to Other Models" section for more details.

To create a foreign key, the general format is "<singular of table>_id", which would point to the table's primary id field. For example, if we had a table of comments that linked to their respective authors (in the users table), by convention you should name the foreign key field user_id.

Foreign keys are also subject to special handling, and should be integers.

Relationships to Other Models

Very commonly, a table will have relationships to other tables. On a blog, users, for example, may have created many comments on many different posts, which may each have many different tags. Here we see several relationships:

            __User__
           /        \
          /          \
     has_many        has_many
        /              \
       /                \
      \/                 \/
    Comments <-has_many- Posts
                        /     /\
                       /        \
                   has_many    has_many
                      \          /
                       \/_    __/
                          Tags

A User object has between zero and infinity Comment objects, and between zero and infinity Post objects associated with it. A Post has between zero and infinity Comments associated with it. A Post is also associated with zero, one, or many Tags, which, in turn, are associated with zero, one, or many Posts.

The reciprocal of has_many is the belongs_to relationship. A Comment "belongs to" a User in the sense that a User created it. A Comment "belongs to" a Post in the sense that the Comment only makes sense in the context of that Post. The relationship between Posts and Tags is fully reciprocal, that is, you cannot easily decide one "owns" another.

All of the relationships are trivially easy to implement in Maveric.

has_one, has_many, & belongs_to

A brief example:

<?php
// models/User.php
class User extends Model
{
  protected $has_many = array(
    'comments',
    'posts'
  );
}
<?php
// models/Comment.php
class Comment extends Model
{
  protected $belongs_to = array(
    'user',
    'post'
  };
}
// views/user/comments.php
<h3>Recent Comments</h3>
<ul>
  <?php foreach($user->comments as $comment): ?>
  <li><?php echo $comment->body; ?> on <?php echo $comment->post->title; ?></li>
  <?php endforeach; ?>
</ul>

As you can see, by defining only the relationship in the class, we can then access the parent, or child, object(s) by name through the original object. These related objects are only loaded when needed, so you don't need to worry about a loop causing infinite database queries.

These relationships can be defined simply, as array entries, or more carefully, as array key-value pairs.

has_many

When an object can "own" zero to multiple child objects, has_many is the correct relationship. An example is the User object, above, which has_many Posts and Comments.

In the example above, the child model was named Comment, the table was "comments", and the comments table had a foreign key named "user_id", so all we needed to do was add 'comments' to the $has_many array.

But what if the comments table's foreign key field was named "author_id"? This is certainly possible. There are two ways we can handle this:

<?php
// models/Comment.php
class Comment extends Model
{
  protected $belongs_to = array(
    'author' => array(
      'model' => 'user'
    )
  );
}

By defining the Comment->author property, Maveric will look for an author_id field, instead of the usual user_id field, but associate it with a User object. We can use this method to rename a related object, which is useful if two objects of the same type may be related, but for different reasons. (For example, an author and an editor.) In this case, the name of the author of the comment could be referenced by $comment->author->username.

We could also change the foreign_key property:

<?php
// models/Comment.php
class Comment extends Model
{
  protected $belongs_to = array(
    'user' => array(
      'foreign_key' => 'author_id'
    )
  );
}

This method just tells Maveric to look for a different column name, but keep the name 'user' and type User for the related object. In this case, the name of the comment author would be in $comment->user->username.

In either case, we will need to modify the User model slightly to tell Maveric to check the right field:

<?php
// models/User.php
class User extends Model
{
  protected $has_many = array(
    'posts',
    'comments' => array(
      'foreign_key' => 'author_id'
    )
  );
}

Finally, what if the comment foreign key is called user_id, but we want to refer to the author property? Then we only need to modify the Comment model:

<?php
// models/Comment.php
class Comment extends Model
{
  protected $belongs_to = array(
    'post',
    'author' => array(
      'model'=>'user',
      'foreign_key'=>'user_id'
    )
  );
}

has_one

has_one is identical to has_many, except that instead of a (possibly empty) array of objects, you get a single object. When adding values to the $has_one array, they should be singular.

<?php
// models/User.php
class User extends Model
{
  protected $has_one = array(
    'profile'
  );
}

// models/Profile.php
class Profile extends Model
{
  protected $belongs_to = array(
    'user'
  );
}

In this example, $user->profile will give you access to the associated Profile object. (And $profile->user would give us access to the user, again.)

has_one or belongs_to

What's the difference between has_one and belongs_to? Practically, the location of the foreign key. If a table has a foreign key, the model should have a $belongs_to entry. If the foreign key is in another table, then the model should have a $has_one entry.

has_and_belongs_to_many

has_and_belongs_to_many describes a many-to-many relationship, where the other relationships are one-to-one or one-to-many. Neither object could be said to properly "own" the other, and neither table has a foreign key for the other. In this case, you'll be using a join table.

Let's look at the tables for Posts and Tags, from the blog example:

posts:

iduser_idtitlebodycreated

tags:

idtag

Since we have a many-to-many relationship, we'll be adding a join table:

posts_tags:

post_idtag_id

If the join table is named like <table1>_<table2>, where the tables are listed in alphabetical order, then we can be very lazy when building the models:

<?php
// models/Tag.php
class Tag extends Model
{
  protected $has_and_belongs_to_many = array(
    'posts'
  );
}

// models/Post.php
class Post extends Model
{
  protected $has_and_belongs_to_many = array(
    'tags'
  );
}

Now we can access the posts array of a Tag object, or the tags array of a Post object. If we add or remove items from either array, the new list will be saved when the base model is saved.

If the join table has a different name, or different column names, you can specify them with the through (table name), key and foreign_key elements. For example, say our join table was not posts_tags but p_t_join, and had columns post and tag.

<?php
// models/Tag.php
class Tag extends Model
{
  protected $has_and_belongs_to_many = array(
    'posts' => array(
      'through' => 'p_t_join',
      'key' => 'tag',
      'foreign_key' => 'post'
    )
  );
}

// models/Post.php
class Post extends Model
{
  protected $has_and_belongs_to_many = array(
    'tags' => array(
      'through' => 'p_t_join',
      'key' => 'post',
      'foreign_key' => 'tag'
    )
  );
}

Notice how the values of key and foreign_key depend on the current model.