Models and Relationships at a Glance

Posted Jan 5, 2009 by Kris Jordan | Comments ( 9 ) | Filed in: PHP | | | | |

The Recess Framework is equip with a lightweight Object-Relational Mapping system that simplifies common database tasks. If you've encountered Active Record style ORMs in other frameworks like Ruby on Rails', Cake's, or Django's the concepts should be familiar: Recess Models use a Djagno-style method chaining API with Rails-style relationships.

A Quick Look

Lets say we need a controller method that is given a user $id and $keyword. The method will find the tags of all posts whose title contains $keyword that was written by the user with id of $id.

/** !Route GET, user/$id/keyword/$keyword/tags */
function getTagsForUserByPostTitleKeyword($id, $keyword) {
	$this->user = new User($id);
	$this->tags = $user
					->posts()
					->like('title',"%$keyword%")
					->tags();
}

In a view we can iterate through the tags with a simple foreach:

foreach($tags as $tag) {
	echo $tag->name, '<br />';
}

Now, let's look at the code for the User, Post, and Tag models that make the above code snippet possible. Not much code at all...

/** !HasMany posts */
class User extends Model { }

/** 
 * !BelongsTo user
 * !HasMany tags, Through: PostsTags
 */
class Post extends Model { }

/**
 * !HasMany posts, Through: PostsTags
 */
class Tag extends Model { }

/**
 * This model represents the join table between the many-to-many
 * Posts <-> Tags relationship.
 *
 * !BelongsTo post
 * !BelongsTo tag
 */
class PostsTags extends Model { }

Creating Models with Recess Tools

The Recess Framework includes a web app to aid development called 'Recess Tools'. Generating new Models for an application and creating corresponding tables in the database is quick work. By browsing to your application and selecting 'new' Model you'll be taken to the new Model helper. After providing a name, table information, and properties the model and, if needed, table will be generated.

A quick peak at the code generated for Post:

<?php
/**
 * !Database Default
 * !Table posts
 */
class Post extends Model {
	/** !Column PrimaryKey, Integer, AutoIncrement */
	public $id;

	/** !Column String */
	public $title;

	/** !Column String */
	public $body;

	/** !Column Integer */
	public $authorId;

}
?>

Lots of annotations! Why so many? Being explicit is a good thing- especially when you don't have to do any of the extra writing. Starting from the top: the Database annotation is what allows Recess to have Models from multiple data sources in a single app. The Table annotation is straightforward: the name of the table in the database the Model maps to. Following is a HasMany relationship using a join table with the Through argument. For Rails folks this should look decently familiar. More to come on relationships.

Each property uses a Column annotation to provide additional semantic typing information. Specifying properties and column mappings is optional in Recess but it is encouraged for three reasons. One, you can look at a Model's code and know exactly which properties are available. This is different from Rails or Cake models. Two, Recess checks to ensure your annotations and the database types match. Three, Recess can regenerate tables from Models marked up with annotations.

Simple Queries

Queries are constructed using a simple API. They're also lazy so queries are not executed until you need the results. The following are some example queries we could perform with the Post model:

$post = new Post();

$allPosts = $post->all(); // Select all Posts

$postsWithPhp = $allPosts->like('title', '%PHP%');

$lastFivePhpPosts = $postsWithPhp->orderBy('id DESC')->limit(5);
// Logically equivalent to:
$lastFivePhpPosts = $allPosts
						->orderBy('id DESC')
						->limit(5)
						->like('title', '%PHP%');

$postWithAuthorId5 = $post->equal('authorId', 5);

The all() method is essentially a SELECT * on the Model's table. Notice how we can take the result of all() and continue refining our query with a LIKE clause on line 3. As mentioned above, Recess Models use lazy evaluation. The code above would not issue any SQL queries unless later in code the results were accessed in a foreach loop or with the array index syntax (i.e. $postsWithAuthorId[0]). Other simple query operators include: notEqual, between, greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo, notLike.

Write to the Database with Insert/Update/Delete

Persisting changes is as simple as calling save() for INSERTs or UPDATEs and delete() for DELETEs. Let's take a look:

$post = new Post();
$post->title = 'Hello World';
$post->body = 'Welcome to Models in Recess!';
$post->save(); // internally calls $post->insert()
$post->title = 'Hello World! With a Bang!';
$post->save(); // internally calls $post->update()
echo 'New Post ID: ', $post->id;

$oldPost = new Post(10); // Post with ID 10
if($oldPost->exists()) {
	$oldPost->delete();
}

$postsWithPhp = new Post();
$postsWithPhp->like('title', '%Ruby%')->delete();

In lines 1-7 a new post is created saved and updated with some filler values. Lines 9-12 delete a Post with an ID of 10. Finally lines 14-15 delete all posts containing 'Ruby' in the title. Recess has support for cascading deletes across relationships and this will be the topic of a future post.

Relationships

A Post belongs to a User and has many Tags. Lets take another look at how this is represented using annotations:

<?php
/**
 * !BelongsTo user, Key: authorId
 * !HasMany tags, Through: PostTag
 */
class Post extends Model { /** Stripped for Brevity */ }
?>

The BelongsTo annotation denotes the 'one' side of a one-to-many relationship. We specify some additional information using the Key modifier to say that the foreign key column name is actually authorId instead of userId which is what it would be by convention. With a belongs to relationship Post has an attached method of user() which will return the User model a post is associated with. It also adds attached methods for setting and unsetting the user: setUser($user) unsetUser(). Attached methods are a low-level feature of Recess written in RecessObject which allow methods to be added to classes dynamically at run time.

The HasMany annotation is a special variant of HasMany because it uses the Through modifier. This tells the HasMany relationship to use a join table thus making it a many-to-many relationship instead of a one-to-many. The HasMany annotation attaches the following methods to the Post class: tags(), addToTags($tag), and removeFromTags($tag).

Chaining it all Together

Queries can be chained across relationships. To revisit the example that started us off lets find all tags of posts with PHP in the title written by a single user:

$this->user = new User($id);
$this->tags = $user->posts()->like('title',"%$keyword%")->tags();

Notice how we're traversing relationships and adding criteria along the way. We could have even more fun with the following:

$this->tags = Make::a('User')
				->equal('id', 1)
				->posts()
				->like('title',"%$keyword%")
				->tags()
				->orderBy('name ASC')
				->limit(10);

The Make::a($className) method is a little short hand to instantiate a new User object and be able to chain methods directly off of it. *sigh* If only PHP allowed (new User)->... but I digress. The point to be made is that as we traverse relationships and add new criteria it is applied to the last Model referenced. So the 'like' after posts applies to Post.

Try Out Recess

So that's a quick, general look at Models in the Recess Framework. Want to try it out? Register for the Recess Preview Program and download away. Happy Modeling!

Comments

Comments are moderated. It may take some time for your comment to appear.

Recess can only be as good as the thoughts that go into it. Let us hear yours...

  1. Posted by SchizoDuckie on Jan 6, 2009
    Isn't there a *lot* of overhead in parsing these annotations versus defining them in-code? If no, what methods did you use to keep it quick?
  2. Posted by Kris Jordan on Jan 6, 2009
    @SchizoDuckie - Annotations are only parsed once and cached behind the scenes when Recess is running in production mode. This turns out to be as fast or faster than building up the data structures in code on every run. To compare with Cake every time a subclass of a model is instantiated in Cake it re-builds that knowledge in the constructor.
  3. Posted by IvanK on Jan 9, 2009
    Sweet, I think this has tons of potential (that's just from what you've showed us, I haven't tied out the code yet - no time :)), but I do have some questions... What about behaviours, or some other ways to extend the models? Migrations? Plugins? And the most obvious question of all - why don't you set up a forum or something, start google groups - you know that kind of thing... Possibly set up something like Cake's Bakery - it'll add a lot of momentum to the framework.

    Keep up the good fight.
  4. Posted by ion gion on Mar 13, 2009
    Nice, this really has potential, but those annotations naming schemes are really awkward, why didn't you use a known @ annotation convention like in most of the languages that support such a feature, you can inspire from this http://code.google.com/p/addendum/wiki/ShortTutorialByExample
  5. Posted by Kris Jordan on Mar 14, 2009
    Ion,

    Great question. Why not use @ syntax? This was a very explicit and well deliberated design decision that came down to this train-of-thought, fundamentally:

    Annotations in PHP are a user-code construct and not a first-class language construct. We are able to implement annotations through the PHP DocComment construct. This is definitely a gray zone and is stretching the DocComment's fundamental purpose. It is a pre-established and well-established convention to use the @ sign to make tags ( http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.pkg.html ). These are tags used for documentation of things like @author, @copyright.

    We deliberately chose not to use the @ sign because we wanted to make it very clear annotations are not documentation tags. Ex:

    /** @author Kris
    * @copyright 2009
    * @hasmany tags
    */

    vs.

    /** @author Kris
    * @copyright 2009
    * !HasMany tags
    */

    Annotations influence your program's behavior - they take action. Java and C# skirt the "ambiguous @" problem because their annotations are language constructs and don't appear within the same context of doc tags. So if we're throwing @ out the door - what better than a bang! It is noticeable, implies action, and is fun.

    Finally, the syntax for Recess Annotations outside of the ! was another important design decision. One that I believe Addendum got fundamentally wrong because it attempted to copy Java's syntax:

    @Annotation({key1 = 1, 2, 3, {4, key = 5}})
    @RolesAllowed({'admin', 'web-editor'})
    @Secured(role = "an admin", level = 2)

    In Recess these would be:

    !Annotation key1: 1, 2, 3, (4, key: 5)
    !RolesAllowed admin, web-editor
    !Secured role: "an admin", level: 2

    If you're going to make up a mini-language for specifying meta-data (which we are :) our thought was we may as well make it as pleasant as possible.

    Hope this helps, thanks for the praise!


  6. Posted by johno on Mar 15, 2009
    Hi!

    I am the author of Addendum library. Looking at this discussion I must agree that using ! instead of @ character to define annotations is a good idea (i might even add this one character to Addendum parser ;-)

    On the other hand I just can't agree with "One that I believe Addendum got fundamentally wrong because it attempted to copy Java's syntax"

    Why "fundamentally" and "wrong"?

    Maybe it's just my point of view, but I've tried to copy Java syntax because it is well known. I have even considered using PHP-alike constructs for annotations to avoid the need to learn new syntax at all.

    Every PHP developer would understand @Annotation(array("role" => "an admin", "level" => 2)) in a split of a second, even without knowing what annotations are. However this would have been a little tedious to write, so I went Java syntax road.

    You instead are creating a completely new mini-language that users must learn. I am not saying that it is badly designed nor bad idea. I just wanted to clarify the decisions behind using Java syntax in Addendum.
  7. Posted by troya1 on May 24, 2009
    Great framework - I got it working with my existing multi-database app in very short order. Using it as a basis for a REST back end for our inventory tables.

    Is there a way to automatically merge database changes into an existing model?

    Also, what about apps that create databases on the fly (allowing better performance and easier scalability)? How would one use Recess models to access such database tables?
  8. Posted by ek on Nov 14, 2009
    Hi people www.recessframework.org! Good resources here. Very useful. www.recessframework.org [url=http://www.jamespot.com/s/38587-.html][/url] or [url=][/url] and else . 38073 - vy [url=][/url] . [url=http://medynamics.net/formacion/user/view.php?id=2805][/url] . for attention thank you
  9. Posted by nayjest on Nov 15, 2009
    I spent a lot of time looking for the ideal framework for php (I like ideology of Django, but I want to use php). I decided to write my framework or use CI/Kohana with some ZF functions (target of my work is specific cms/cmf). And so, accidentally stumbled on it in the comments on the blog Doctrine ORM. In Russia / Ukraine of such a framework not even heard.
    But it looks realy great! (Maby I will write some articles about Recess/translate documentation on russian later)
    The main advantage is a speed of developent, its looks great here.
    Sorry for my English :)
    Respect!

You must login to comment on this page.