Book Image

Yii Project Blueprints

By : Charles R. Portwood ll
Book Image

Yii Project Blueprints

By: Charles R. Portwood ll

Overview of this book

Table of Contents (15 chapters)
Yii Project Blueprints
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Creating models with Gii


Now that our database has been created, we can create models for our database table. To create our models, we are going to use Gii, Yii's built-in code generator.

Open up your web browser and navigate to http://localhost/gii (in this book, we will always use localhost as our working hostname for our working project. If you are using a different hostname, replace localhost with your own). Once loaded, you should see the Yii Code Generator, as shown in the following screenshot:

Tip

If you aren't able to access Gii, verify that your web server has rewriting enabled. Information about how to properly configure your web server for Yii can be found at (http://www.yiiframework.com/doc/guide/1.1/en/quickstart.apache-nginx-config).

Click on the link titled Model Generator, and then fill in the form on the page that appears. The table name should be set to tasks. The model name should prepopulate. If it doesn't, set the model name to Tasks, and then click on preview. Once the page has reloaded, you can preview what the model will look like before clicking on the Generate button to write your new model to your protected/models/ directory. Once you have generated your model for tasks, repeat the process for projects.

Enhancing the models

Now that our models have been created, there are several sections that should be modified.

Updating the default validation rules

The first part of our model that needs to be modified is the validation rules. Validation rules in Yii are stored in the model's rules() method and are executed when the model's validate() method is called. Starting with our tasks model, we can see that Gii has already prepopulated our validation rules for us based upon our database.

There are several fields of this model that we would like to always have set, namely, project_id, title, the task itself, and whether or not it has been completed. We can make these fields required in our model by adding a new array to our rules section, as follows:

array('project_id, title, data, completed', 'required')

By making these fields required in our model, we can make client- and server-side validation easier when we start making forms. Our final method for this model will look as follows:

public function rules()
{
        return array(
            array('project_id, completed, due_date, created, updated', 'numerical', 'integerOnly'=>true),
		   array('project_id, title, data, completed', 'required'),
            array('title, data', 'safe'),
            array('id, title, data, project_id, completed, due_date, created, updated', 'safe', 'on'=>'search'),
        );
}

Our project's models should also be changed so that the project name and its completed status are required. We can accomplish this by adding the following to our validation rules array:

array('name, completed', 'required')

Tip

Additional validation rules can be found in the Yii wiki at http://www.yiiframework.com/wiki/56/

Defined relations

Another component of our model that we should change is the relations() method. By declaring model relations in Yii, we can take advantage of the ability of ActiveRecords to automatically join several related models together and retrieve data from them without having to explicitly call that model for its data.

For example, once our model relations are set up, we will be able to retrieve the project name from the Tasks model, as follows:

Tasks::model()->findByPk($id)->project->name;

Before we can declare our relations though, we need to determine what the relations actually are. Since SQLite does not support foreign key relations, Gii was unable to automatically determine the relations for us.

In Yii, there are four types of relations: BELONGS_TO, HAS_MANY, HAS_ONE, and MANY_MANY. Determining the relation type can be done by looking at the foreign key for a table and asking which relational type fits best based upon the data that the table will store. For this application, this question can be answered as follows:

  • Tasks belong to a single project

  • A project has one or many tasks

Now that we have determined our relationship types between our two tables, we can write the relations. Starting with the tasks table, replace the relations() method with the following:

public function relations()
{
return array(
        'tasks' => array(self::HAS_MANY, 'Task', 'project_id')
    );
}

The syntax for the relations array is as follows:

'var_name'=>array('relationship_type', 'foreign_model', 'foreign_key', [... other options ..])

For our projects model, our relations() method looks like this:

public function relations()
{
    return array(
        'tasks' => array(self::HAS_MANY, 'Tasks', 'project_id')
    );
}

Removing tasks when a project is deleted

In our model's current state, whenever a project is deleted, all the tasks associated with it become orphaned. One way of dealing with this edge case is to simply delete any tasks associated with the project. Rather than writing code to handle this in the controller, we can have the model take care of it for us by referencing the project's model's beforeDelete() method as follows:

public function beforeDelete()
{
    Tasks::model()->deleteAllByAttributes(array('project_id' => $this->id));
    return parent::beforeDelete();
}

Retrieving the project metadata

There is also metadata about a project that we cannot obtain directly from the projects database table. This data includes the number of tasks a project has, as well as the number of completed tasks a project has. We can obtain this from our model by creating two new methods in the project's model, as follows:

public function getNumberOfTasks()
{
    return Tasks::model()->countByAttributes(array('project_id' => $this->id));
}

public function getNumberOfCompletedTasks()
{
     return Tasks::model()->countByAttributes(array('project_id' => $this->id, 'completed' => 1));
}

Additionally, we can determine the progress of a project by getting a percentage of completed tasks versus the total number of tasks, as follows:

public function getPercentComplete()
{
    $numberOfTasks = $this->getNumberOfTasks();
    $numberOfCompletedTasks = $this->getNumberOfCompletedTasks();

    if ($numberOfTasks == 0)
        return 100;
    return ($numberOfCompletedTasks / $numberOfTasks) * 100;
}

Automatically setting the created and updated time

The last change needed to be made to the models is to enable them to automatically set the created and updated timestamp in the database every time the model is saved. By moving this logic into the models, we can avoid having to manage it either in the forms that submit the data or in the controllers that will process this data. This change can be made by adding the following to both models:

public function beforeSave()
{
    if ($this->isNewRecord)
         $this->created = time();

    $this->updated = time();

    return parent::beforeSave();
}

In the beforeSave() method, the updated property is always set every time the model is saved, and the created property is only set if ActiveRecord considers this to be a new record. This is accomplished by checking the isNewRecord property of the model. Additionally, both properties are set to time(), the PHP function used to get the current Unix timestamp.

The last piece of code that is important in this method is return parent::beforeSave();. When Yii's save() method is called, it checks that beforeSave() returns true before saving the data to the database. While we could have this method return true, it's easier to have it return whatever the parent model (in this case CActiveRecord) returns. It also ensures that any changes made to the parent model will get carried to the model.

Tip

Since the beforeSave() method is identical for both models, we could also create a new model that only extended CActiveRecord and only implemented this method. The tasks and projects model will then extend that model rather than CActiveRecord and will inherit this functionality. Moving shared functionality to a shared location reduces the number of places where code needs to be written and, consequently, the number of places a bug can show up in.