Models and Relationships at a Glance
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
Recess can only be as good as the thoughts that go into it. Let us hear yours...
Keep up the good fight.
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!
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.
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?
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!
Question: can you replace the ORM easily?
gxgfqn, http://SCOREADVANCE.NET , etkxssv
xqxxsy, http://mercedesofsouthatlanta.com, How To Lose Weight Fast uwhlhar