How to create a blog with the Recess PHP Framework, Part 3

October 28, 2009

This is the third article in my Recess Framework blog tutorial that will demonstrate how to create model relationships with the Recess PHP Framework. Previously in this tutorial, we learned how to list and display individual blog posts, and more recently, how to create, update and delete blog posts using Recess’ ORM (object relational mapper). This tutorial assumes you have completed the previous tutorials. If you have not completed the previous tutorials, you should download the most current version of our blog application before reading further.

As I explained in my previous tutorial, [BLOG_ROOT] references the filesystem directory where you have installed the Recess PHP Framework; on my machine, [BLOG_ROOT] is ~/Sites/recess/. [BLOG_URL] references the URL with which you can access the blog application in a web browser; on my machine [BLOG_URL] is http://localhost/~joshlockhart/recess/.

Authors

We will create a one-to-many model relationship between authors and blog posts. One author writes many blog posts, and each blog post belongs to only one author. If we were using a user authentication system, we could determine the author based on the logged-in user writing the post. We will explore user authentication with Recess later in this tutorial series. For now, we will display a combo-box on the Write Post and Edit Post forms that will display a list of available authors. Let’s get started. We first need to create the controller, models, and views for authors. Since this process was discussed in detail in the previous two two tutorials, I will move quickly through this process assuming you are familiar with these steps.

Creating the Author database table

Create the authors table in your SQLite database with the following SQL commands:

CREATE TABLE `authors` (
    `id` INTEGER PRIMARY KEY,
    `name_first` TEXT,
    `name_last` TEXT
);
INSERT INTO `authors` VALUES (1,'John','Doe');
INSERT INTO `authors` VALUES (2,'Jane','Doe');

Next, update the posts database table and add an “author_id” foreign key:

ALTER TABLE `posts` ADD `author_id` INTEGER;    

Last, let’s make sure all existing posts are assigned to the first author.

UPDATE `posts` SET `author_id` = 1;

Creating the Author model

Next, create the Author model at [BLOG_ROOT]apps/blog/models/Author.class.php and insert the following code:

<?php
/** 
 * !Table authors
 */
class Author extends Model {}
?>

Creating the Author controller

The Author controller is responsible for listing, creating, updating, and deleting blog authors. Create [BLOG_ROOT]apps/blog/controllers/AuthorsController.class.php and insert the following code:

<?php
Library::import('recess.framework.controllers.Controller');
Library::import('blog.models.Author');

/**
 * !RespondsWith Layouts
 * !Prefix Routes: /, Views: authors/
 */
class AuthorsController extends Controller {

    /** !Route GET, authors */
    public function listAuthors(){
        $author = new Author();
        $this->authors = $author->all();
    }

    /** !Route GET, authors/$id */
    public function show($id){
        $author = new Author($id);
        $this->author = $author->find()->first();
    }

    /** !Route GET, authors/new */
    public function newAuthor(){
        $this->author = new Author();
    }

    /** !Route POST, authors */
    public function create(){
        $author = new Author($this->request->post['author']);

        if( $author->save() ){
            return $this->redirect($this->urlTo('listAuthors'));
        } else {
            return $this->ok('newAuthor');
        }
    }

    /** !Route GET, authors/$id/edit */
    public function edit($id){
        $author = new Author($id);

        if( $author->exists() ){
            $this->author = $author->find()->first();
        } else {
            return $this->redirect($this->urlTo('listAuthors'));
        }
    }

    /** !Route PUT, authors/$id */
    public function update($id){
        $author = new Author($id);

        if( $author->exists() ){
            $this->author = $author->find()->first()->copy($this->request->put['author']);
            if( $this->author->save() ){
                //set a success message if necessary
            }
            return $this->redirect($this->urlTo('edit',$id));
        } else {
            return $this->redirect($this->urlTo('listAuthors'));
        }
    }

    /**
     * !Route DELETE, authors/$id
     * !Route GET, authors/$id/delete
     */
    public function delete($id){
        $author = new Author($id);

        if( $author->exists() ) $author->delete();
        return $this->redirect($this->urlTo('listAuthors'));
    }

}

?>

Creating the Author view templates

Now, let’s quickly create the templates for the Author CRUD actions. Create [BLOG_ROOT]apps/blog/views/authors/listAuthors.html.php and insert the following markup:

<html>
    <body>
        <h1>Authors</h1>
        <ul>
            <?php foreach( $authors as $author ): ?>
            <li>
                <h2><a href="<?php echo Url::action('AuthorsController::show',$author->id); ?>"><?php echo $author->name_first . ' ' . $author->name_last; ?></a></h2>
                <p>
                    <a href="<?php echo Url::action('AuthorsController::edit',$author->id); ?>">Edit</a>
                    <a href="<?php echo Url::action('AuthorsController::delete',$author->id); ?>" onclick="return confirm('Are you sure?');">Delete</a>
                </p>
            </li>
            <?php endforeach; ?>
        </ul>
    </body>
</html>

You can view the author list at [BLOG_URL]index.php/blog/authors/. Next, create [BLOG_ROOT]apps/blog/views/authors/show.html.php and insert the following markup:

<html>
    <body>
        <h1><?php echo $author->name_first . ' ' . $author->name_last; ?></h1>
    </body>
</html>    

You can view each author’s page at [BLOG_URL]index.php/blog/authors/1 (where ‘1’ is the ID of a given author). Next, create [BLOG_ROOT]apps/blog/views/authors/newAuthor.html.php and insert the following markup:

<html>
    <body>
        <h1>Add Author</h1>
        <form action="<?php echo Url::action('AuthorsController::create'); ?>" method="POST">
            <fieldset>
                <label for="firstName">First Name</label><br/>
                <input type="text" id="firstName" name="author[name_first]" value=""/><br/>
                <label for="lastName">Last Name</label><br/>
                <input type="text" id="lastName" name="author[name_last]" value=""/><br/>
                <input type="submit" value="Add Author"/>
            </fieldset>
        </form>
    </body>
</html>    

You can view the Add Author form at [BLOG_URL]index.php/blog/authors/new. Next, create [BLOG_ROOT]apps/blog/views/authors/edit.html.php and insert the following markup:

<html>
    <body>
        <h1>Edit Author</h1>
        <form action="<?php echo Url::action('AuthorsController::update',$author->id); ?>" method="POST">
            <fieldset>
                <label for="firstName">First Name</label><br/>
                <input type="text" id="firstName" name="author[name_first]" value="<?php echo $author->name_first; ?>"/><br/>
                <label for="lastName">Last Name</label><br/>
                <input type="text" id="lastName" name="author[name_last]" value="<?php echo $author->name_last; ?>"/><br/>
                <input type="hidden" name="_METHOD" value="PUT"/>
                <input type="submit" value="Update Author"/>
            </fieldset>
        </form>
    </body>
</html>    

You can view the Edit Author form at [BLOG_URL]index.php/blog/authors/1/edit (where ‘1’ is the ID of a given author). Next, create blank template files at [BLOG_ROOT]apps/blog/views/authors/create.html.php and [BLOG_ROOT]apps/blog/views/authors/update.html.php and [BLOG_ROOT]apps/blog/views/authors/delete.html.php. Although these templates are not necessary for our application, Recess requires a template for each public controller action to avoid throwing errors. The creators of Recess will remedy this behavior in a future framework update.

We are now able to list, view, add, update, and delete blog authors. Now let’s relate authors to posts.

Associating Authors to Posts

Updating the models

We need to tell Recess that each post belongs to one author. Open [BLOG_ROOT]apps/blog/models/Author.class.php and update it’s contents to look like this:

<?php
/** 
 * !Table authors
 * !HasMany posts, Key: author_id
 */
class Author extends Model {}
?>

We use a Recess declarative annotation to describe our Author model. In this example, we say that an Author !HasMany posts. We also specify the foreign key column used by the related model if the foreign key column name is different from convention; by default, Recess expects a foreign key column name of “authorId”. I like to suffix my foreign keys with "_id", so I needed to clarify this in the !HasMany annotation. Let’s now update [BLOG_ROOT]apps/blog/models/Post.class.php:

<?php
/**
 * !Table posts
 * !BelongsTo author, Key: author_id
 */
class Post extends Model {}
?>

We use a Recess declarative annotation to describe our Post model; a Post !BelongsTo one author. We also specify the foreign key column used by this relationship. The annotation on the Post model assumes the posts database table has an “author_id” foreign key column that references an existing Author database record. We added this foreign key earlier in this tutorial.

Updating the views

Let’s now update our Write Post and Edit Post templates so that we may select an author when we write or edit a post. Before we edit the templates, we need to make sure we pass an array of available authors from the PostsController::write() and PostsController::edit() actions. Open [BLOG_ROOT]apps/blog/controllers/PostsController.class.php. Edit the PostsController::write() action to look like this:

/** !Route GET, posts/write */
public function write(){
    $this->post = new Post();
    $author = new Author();
    $this->authors = $author->all();
}

And update the PostsController::edit() action to look like this:

/** !Route GET, posts/$id/edit */
public function edit($id){
    $post = new Post($id);

    if( $post->exists() ){
        $this->post = $post->find()->first();
        $author = new Author();
        $this->authors = $author->all();
    } else {
        return $this->redirect($this->urlTo('listPosts'));
    }
}

Since we reference the Author model in the Posts controller, we need to import the Author model into our controller. Add this line to the existing import statements at the top of PostsController.class.php:

Library::import('blog.models.Author');    

Now we can update our templates. Open [BLOG_ROOT]apps/blog/views/home/write.html.php and update it to look like this. I have highlighted the changes.

<html>
    <body>
        <h1>Write Post</h1>
        <form action="<?php echo Url::action('PostsController::create'); ?>" method="POST">
            <fieldset>
                <label for="title">Title</label><br/>
                <input type="text" id="title" name="post[title]" value=""/><br/>    
                <label for="content">Content</label><br/>
                <textarea id="content" name="post[content]" rows="20" cols="50"></textarea><br/>
                <label for="author">Select an author:</label><br/>
                <select id="author" name="post[author_id]">
                    <?php foreach( $authors as $author ): ?>
                    <option value="<?php echo $author->id; ?>"><?php echo $author->name_first .' '.$author->name_last; ?></option>
                    <?php endforeach; ?>
                </select><br/><br/>
                <input type="submit" value="Create Post"/>
            </fieldset>
        </form>
    </body>
</html>

Next, open [BLOG_ROOT]apps/blog/views/home/edit.html.php and update the markup so it looks like this. I have highlighted the changes.

<html>
    <body>
        <h1>Update Post</h1>
        <form action="<?php echo Url::action('PostsController::update',$post->id); ?>" method="POST">
            <fieldset>
                <label for="title">Title</label><br/>
                <input type="text" id="title" name="post[title]" value="<?php echo $post->title; ?>"/><br/>    
                <label for="content">Content</label><br/>
                <textarea id="content" name="post[content]" rows="20" cols="50"><?php echo $post->content; ?></textarea><br/>
                <label for="author">Select an author:</label><br/>
                <select id="author" name="post[author_id]">
                    <?php foreach( $authors as $author ): ?>
                    <option value="<?php echo $author->id; ?>" <?php if($author->id == $post->author_id): ?>selected="selected"<?php endif; ?>><?php echo $author->name_first .' '.$author->name_last; ?></option>
                    <?php endforeach; ?>
                </select><br/><br/>
                <input type="hidden" name="_METHOD" value="PUT"/>
                <input type="submit" value="Update Post"/>
            </fieldset>
        </form>
    </body>
</html>

When we edit an existing post, the correct author will be pre-selected.

Relationships in Action

We have successfully related Authors to Posts. Let’s see an example of this relationship in action by displaying all posts written by an author on the Show Author page. Open [BLOG_ROOT]apps/blog/views/authors/show.html.php and update its markup like this:

<html>
    <body>
        <h1><?php echo $author->name_first . ' ' . $author->name_last; ?></h1>
        <ul>
            <?php foreach( $author->posts() as $post ): ?>
            <li><a href="<?php echo Url::action('PostsController::show',$post->id); ?>"><?php echo $post->title; ?></a></li>
            <?php endforeach; ?>
        </ul>
    </body>
</html>

Last, let’s place a link to the author’s page beneath each blog post’s title. Open [BLOG_ROOT]apps/blog/views/home/listPosts.html.php and update the markup as shown below. The changes are highlighted.

<html>
    <body>
        <h1>Posts</h1>
        <ul>
            <?php foreach( $posts as $post ): ?>
            <li>
                <h2><a href="<?php echo Url::action('PostsController::show',$post->id); ?>"><?php echo $post->title; ?></a></h2>
                <p>By <a href="<?php echo Url::action('AuthorsController::show',$post->author()->id); ?>"><?php echo $post->author()->name_first . ' ' . $post->author()->name_last; ?></a> | Posted on <?php echo strftime("%B %e, %Y", $post->created); ?></p>
                <p><?php echo $post->content; ?></p>
                <p>
                    <a href="<?php echo Url::action('PostsController::edit',$post->id); ?>">Edit</a>
                    <a href="<?php echo Url::action('PostsController::delete',$post->id); ?>" onclick="return confirm('Are you sure?');">Delete</a>
                </p>
            </li>
            <?php endforeach; ?>
        </ul>
    </body>
</html>

Also open [BLOG_ROOT]apps/blog/views/home/show.html.php and update the markup as shown below. The changes are highlighted:

<html>
    <body>
        <h1><?php echo $post->title; ?></h1>
        <p>By <a href="<?php echo Url::action('AuthorsController::show',$post->author()->id); ?>"><?php echo $post->author()->name_first . ' ' . $post->author()->name_last; ?></a> | Posted on <?php echo strftime("%B %e, %Y", $post->created); ?></p>
        <p><?php echo $post->content; ?></p>
    </body>
</html>

Summary

This tutorial demonstrated how to relate the Author and Post models using declarative annotations. We also learned how to declare a custom foreign key on model relationships if the foreign key column name does not adhere to the Recess Framework naming convention. And finally, we learned how to take advantage of model relationships with the Recess Framework ORM in our templates. The next tutorial in this series will explore RESTful routing in the Recess Framework. Stay tuned!

Download the final project

This file contains the Recess Framework and all controllers, models, and views for this tutorial.

Download (ZIP, 356 KB)

Comments

Francesco's avatar
Francesco
about this take a look about https://github.com/Javanile/SchemaDB it very simple ain on automatic generation of database schema based on class-models

Leave a comment