Implementing the ActiveRecord class and its methods

Now, we can try the tests and see them not passing before we go through the implementation of the class. So, just run the following command, as we learned in the previous chapter:

$ cd tests
$ ../vendor/bin/codecept run unit

It's very probable that the preceding command will fail with the following error:

PHP Fatal error:  Call to undefined method appmodelsUser::tableName()

This is because our class has not yet been regenerated as ActiveRecord.

In the next section, we will start the work of making our tests pass by starting with the migrations to move some information into the database and progress from there.

Dealing with migrations

So, the best step forward is to define a user table in the database, fill it with the data we would need, and then implement the user model on top of it with the required methods from the interface.

Note

There is more to be said about migrations and the documentation about it is being improved and expanded every day. Be sure to head over and have a read for yourself at http://www.yiiframework.com/doc-2.0/guide-db-migrations.html.

Let's start by creating the migration:

$ ./yii migrate/create table_create_user
Yii Migration Tool (based on Yii v2.0.0-dev)

Create new migration '/var/www/vhosts/htdocs/migrations/m140906_172836_table_create_user.php'? (yes|no) [no]:yes
New migration created successfully.
$

Now that we have the migration, let's implement the up() and down() methods as needed:

// migrations/m140906_172836_table_create_user.php

class m140906_172836_table_create_user extends Migration
{
    public function up()
    {
        $this->createTable('user', [
            'id' => 'pk',
            'username' => 'varchar(24) NOT NULL',
            'password' => 'varchar(128) NOT NULL',
            'authkey' => 'varchar(255) NOT NULL',
            'accessToken' => 'varchar(255)'
        ]);

        $this->insert('user', [
            'username' => 'admin',
            'password' => Yii::$app->getSecurity()->generatePasswordHash('admin'),
            'authkey' => uniqid()
        ]);
    }

    public function down()
    {
        $this->dropTable('user'),
    }
}

We use the Security component provided by Yii to create the password. There are many other functions that are quite handy and avoid the need to reinvent the wheel.

Please note that it's important to implement the down() method correctly and test it before pushing your changes, as it will be fundamental if you need to revert to a previous state of the application, also called a roll back.

Tip

In addition to up() and down(), you can use safeUp() and safeDown(), which will allow you to run the migration up or down using transactions, which in turn means that in the case of an error, all prior operations will be rolled back automatically.

The migrations are implemented and used in the same way as in the case of Yii 1, and if you've never used them before, they're a great tool as they give you the ability to define specific steps that can be easily missed when deploying your application. The syntax used should also be quite straightforward to understand and the methods self-explanatory: createTable(), renameColumn(), addForeignKey(), and so on.

Now that we have our migration in place, it's time to apply it by running the following command:

$ ./yii migrate/up
Yii Migration Tool (based on Yii v2.0.0-dev)

Creating migration history table "migration"...done.
Total 1 new migration to be applied:
    m140906_172836_table_create_user
Apply the above migration? (yes|no) [no]:yes
*** applying m140906_172836_table_create_user
    > create table user ... done (time: 0.022s)
    > insert into user ... done (time: 0.008s)
*** applied m140906_172836_table_create_user (time: 0.585s)
Migrated up successfully.

Now that we have the structure and the data in the database, we can start refactoring the model accordingly.

The Gii code generation tool

Yii continues to provide and improve its system of code generation tools, particularly Gii. Gii is a code generator that helps you create the basic code structure for models, controllers, CRUD, modules, and so forth, so that you don't have to think too much about what needs to be done and instead, get to the implementation part as quickly as possible.

The basic application that we're using comes with Gii (and it's defined as a require-dev package). And, since in our case we're running the tests in a (virtual) hosted environment, we need to adjust the configuration a little bit; we need to allow our client IP to access the tool:

// config/web.php

if (YII_ENV_DEV) {
    // ...
    $config['bootstrap'][] = 'gii';
    $config['modules']['gii'] = [
        'class' => 'yiigiiModule',
        'allowedIPs' => ['127.0.0.1', '::1', '192.168.56.*'],
    ];
}

192.168.56.* should be the default case if you're using VirtualBox.

Now that we have made this change, we can head our browsers to http://basic.yii2.sandbox/gii. From there, we can click on the Model Generator section, where we can create the new model, as shown in the following screenshot:

The Gii code generation tool

The model generator interface

When clicking on the Preview button, Gii will first check whether the file(s) to be generated already exist and give us the opportunity to see the difference and decide whether we want to override the file(s) before we actually hit the Generate button.

Since our User model is so thin at the moment, we won't have any problems in overwriting it and re-implementing the needed methods ourselves. Just remember to tick the Overwrite check box and click on Generate. Otherwise, you can just adjust it accordingly with the hints given in the following paragraphs.

After clicking on Generate, you should be able to see the The code has been generated successfully notice at the end of the page.

Now let's head back to our User.php class and see what's been changed, and refine the implementation.

First of all, we will notice that the class now extends from the ActiveRecord class; this is the default class for database-facing models. There is a series of default methods already implemented, which we won't need to change. What we would need instead is to make the class implement IdentityInterface, as follows:

// models/User.php

use Yii;
use yiidbActiveRecord;
use yiiwebIdentityInterface;

class User extends ActiveRecord implements IdentityInterface
{

Now, implement the five required methods from IdentityInterface at the end of the class:

// models/User.php

/**
 * @inheritdoc
 */
public static function findIdentity($id) {
    return self::findOne($id);
}

As we can see, the way to find a record in the database is quite straightforward, as ActiveRecord exposes some very nifty and easy-to-understand methods to interact with the database. We will be seeing plenty of these use cases across the upcoming pages.

It's probably worth noticing that findIdentity() returns an IdentityInterface object. The previous implementation that we overwrote invoked new static(), which, in turn, triggered the magic method __construct() from the yiiaseObject class.

Note

The new static() method has been available since PHP 5.3 and provides a way to instantiate a child class statically from a parent, which wasn't possible earlier. This methodology is called Late Static Binding.

More information can be found in the PHP manual at http://php.net/manual/en/language.oop5.late-static-bindings.php.

As mentioned earlier, findIdentityByAccessToken is not needed as accessToken will not be used anywhere in our code, so let's implement it:

public static function findIdentityByAccessToken($token, $type = null) {
    throw new NotSupportedException('Login by access token not supported.'),
}

The remaining three methods from the interface should be straightforward to implement and understand, and to do so, we can use the following code:

public function getId() {
    return $this->id;
}

public function getAuthKey() {
    return $this->authkey;
}

public function validateAuthKey($authKey) {
    return $this->authkey === $authKey;
}

Left out from the obvious methods from the interface are a couple of methods that are used in LoginForm.php; one of them is findByUsername, which is as follows:

/**
 * Finds user by username
 *
 * @param  string      $username
 * @return static|null
 */
public static function findByUsername($username)
{
    return self::findOne(['username' => $username]);
}

Another is validatePassword, which is as follows:

/**
 * Validates password
 *
 * @param  string  $password password to validate
 * @return boolean if password provided is valid for current user
 */
public function validatePassword($password)
{
    return Yii::$app->getSecurity()->validatePassword($password, $this->password);
}

Here, we again use the validatePassword() method from the Security component, which makes the use of cryptography and any additional level of security that we want to add transparent to the user.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset