Changes between Initial Version and Version 1 of Models

Show
Ignore:
Timestamp:
05/19/09 14:50:40 (16 months ago)
Author:
james
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Models

    v1 v1  
     1= Models = 
     2 
     3Models 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. 
     4 
     5Models 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. 
     6 
     7== Basic Example == 
     8 
     9Let's say we have a table called 'users', with the following structure: 
     10 
     11||'''id'''||'''username'''||'''password'''|| 
     12 
     13All 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}}}: 
     14 
     15{{{ 
     16#!php 
     17<?php 
     18/** 
     19 * User model 
     20 * models/User.php 
     21 */ 
     22class User extends Model {} 
     23}}} 
     24 
     25That's it! Let's populate our table with some dummy data: 
     26 
     27||'''id'''||'''username'''||'''password'''|| 
     28||1||john||hashed1|| 
     29||2||james||hashed2|| 
     30 
     31Now let's see how easy it is to access a row in the table: 
     32 
     33{{{ 
     34#!php 
     35<?php 
     36/** 
     37 * Somewhere in a Controller 
     38 */ 
     39 
     40$user = new User(1); 
     41 
     42echo $user->username; // echos "john" 
     43}}} 
     44 
     45== Creating Tables == 
     46 
     47Every 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. 
     48 
     49Tables 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: 
     50 
     51{{{ 
     52#!php 
     53<?php 
     54/** 
     55 * User model 
     56 * models/User.php 
     57 */ 
     58class User extends Model 
     59{ 
     60  protected $table = 'userdata'; // put the table name here 
     61} 
     62}}} 
     63 
     64=== id === 
     65 
     66The primary key of each table should be named {{{id}}} and should be an auto-incrementing integer. In MySQL, the column definition would be 
     67 
     68{{{ 
     69#!sql 
     70id INT NOT NULL AUTO_INCREMENT PRIMARY KEY 
     71}}} 
     72 
     73A {{{BIGINT}}} or other long integer type could also be used, but PHP's handling of large integers is limited. 
     74 
     75=== created & updated === 
     76 
     77Two 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. 
     78 
     79=== Foreign Keys === 
     80 
     81''See the "Relationships to Other Models" section for more details.'' 
     82 
     83To 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}}}. 
     84 
     85Foreign keys are also subject to special handling, and should be integers. 
     86 
     87== Relationships to Other Models == 
     88 
     89Very 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: 
     90 
     91{{{ 
     92            __User__ 
     93           /        \ 
     94          /          \ 
     95     has_many        has_many 
     96        /              \ 
     97       /                \ 
     98      \/                 \/ 
     99    Comments <-has_many- Posts 
     100                        /     /\ 
     101                       /        \ 
     102                   has_many    has_many 
     103                      \          / 
     104                       \/_    __/ 
     105                          Tags 
     106}}} 
     107 
     108A 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. 
     109 
     110The 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. 
     111 
     112All of the relationships are trivially easy to implement in Maveric. 
     113 
     114=== has_one, has_many, & belongs_to === 
     115 
     116A brief example: 
     117 
     118{{{ 
     119#!php 
     120<?php 
     121// models/User.php 
     122class User extends Model 
     123{ 
     124  protected $has_many = array( 
     125    'comments', 
     126    'posts' 
     127  ); 
     128} 
     129}}} 
     130 
     131{{{ 
     132#!php 
     133<?php 
     134// models/Comment.php 
     135class Comment extends Model 
     136{ 
     137  protected $belongs_to = array( 
     138    'user', 
     139    'post' 
     140  }; 
     141} 
     142}}} 
     143 
     144 
     145{{{ 
     146#!php 
     147// views/user/comments.php 
     148<h3>Recent Comments</h3> 
     149<ul> 
     150  <?php foreach($user->comments as $comment): ?> 
     151  <li><?php echo $comment->body; ?> on <?php echo $comment->post->title; ?></li> 
     152  <?php endforeach; ?> 
     153</ul> 
     154}}} 
     155 
     156As 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. 
     157 
     158These relationships can be defined simply, as array entries, or more carefully, as array key-value pairs. 
     159 
     160==== has_many ==== 
     161 
     162When 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. 
     163 
     164In 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. 
     165 
     166But 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: 
     167 
     168{{{ 
     169#!php 
     170<?php 
     171// models/Comment.php 
     172class Comment extends Model 
     173{ 
     174  protected $belongs_to = array( 
     175    'author' => array( 
     176      'model' => 'user' 
     177    ) 
     178  ); 
     179} 
     180}}} 
     181 
     182By 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}}}. 
     183 
     184We could also change the {{{foreign_key}}} property: 
     185 
     186{{{ 
     187#!php 
     188<?php 
     189// models/Comment.php 
     190class Comment extends Model 
     191{ 
     192  protected $belongs_to = array( 
     193    'user' => array( 
     194      'foreign_key' => 'author_id' 
     195    ) 
     196  ); 
     197} 
     198}}} 
     199 
     200This 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}}}. 
     201 
     202In either case, we will need to modify the User model slightly to tell Maveric to check the right field: 
     203 
     204{{{ 
     205#!php 
     206<?php 
     207// models/User.php 
     208class User extends Model 
     209{ 
     210  protected $has_many = array( 
     211    'posts', 
     212    'comments' => array( 
     213      'foreign_key' => 'author_id' 
     214    ) 
     215  ); 
     216} 
     217}}} 
     218 
     219Finally, 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: 
     220 
     221{{{ 
     222#!php 
     223<?php 
     224// models/Comment.php 
     225class Comment extends Model 
     226{ 
     227  protected $belongs_to = array( 
     228    'post', 
     229    'author' => array( 
     230      'model'=>'user', 
     231      'foreign_key'=>'user_id' 
     232    ) 
     233  ); 
     234} 
     235}}} 
     236 
     237==== has_one ==== 
     238 
     239{{{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. 
     240 
     241{{{ 
     242#!php 
     243<?php 
     244// models/User.php 
     245class User extends Model 
     246{ 
     247  protected $has_one = array( 
     248    'profile' 
     249  ); 
     250} 
     251 
     252// models/Profile.php 
     253class Profile extends Model 
     254{ 
     255  protected $belongs_to = array( 
     256    'user' 
     257  ); 
     258} 
     259}}} 
     260 
     261In this example, {{{$user->profile}}} will give you access to the associated Profile object. (And {{{$profile->user}}} would give us access to the user, again.) 
     262 
     263==== has_one or belongs_to ==== 
     264 
     265What'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. 
     266 
     267=== has_and_belongs_to_many === 
     268 
     269{{{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. 
     270 
     271Let's look at the tables for Posts and Tags, from the blog example: 
     272 
     273posts: 
     274||'''id'''||'''user_id'''||'''title'''||'''body'''||'''created'''|| 
     275 
     276tags: 
     277||'''id'''||'''tag'''|| 
     278 
     279Since we have a many-to-many relationship, we'll be adding a join table: 
     280 
     281posts_tags: 
     282||'''post_id'''||'''tag_id'''|| 
     283 
     284If 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: 
     285 
     286{{{ 
     287#!php 
     288<?php 
     289// models/Tag.php 
     290class Tag extends Model 
     291{ 
     292  protected $has_and_belongs_to_many = array( 
     293    'posts' 
     294  ); 
     295} 
     296 
     297// models/Post.php 
     298class Post extends Model 
     299{ 
     300  protected $has_and_belongs_to_many = array( 
     301    'tags' 
     302  ); 
     303} 
     304}}} 
     305 
     306Now 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. 
     307 
     308If 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}}}. 
     309 
     310{{{ 
     311#!php 
     312<?php 
     313// models/Tag.php 
     314class Tag extends Model 
     315{ 
     316  protected $has_and_belongs_to_many = array( 
     317    'posts' => array( 
     318      'through' => 'p_t_join', 
     319      'key' => 'tag', 
     320      'foreign_key' => 'post' 
     321    ) 
     322  ); 
     323} 
     324 
     325// models/Post.php 
     326class Post extends Model 
     327{ 
     328  protected $has_and_belongs_to_many = array( 
     329    'tags' => array( 
     330      'through' => 'p_t_join', 
     331      'key' => 'post', 
     332      'foreign_key' => 'tag' 
     333    ) 
     334  ); 
     335} 
     336}}} 
     337 
     338Notice how the values of {{{key}}} and {{{foreign_key}}} depend on the current model.