Yii 2.0: Creating a Simple CRUD App With Yii2 (Revised 12/20/2013)

21 followers

Creating a Simple CRUD App with Yii Framework 2 (Updated December 20th, 2013)

By popular demand, today I will be revisiting my previous Yii Framework 2 Getting Started Guide and updating it with what is currently available in the Yii Framework 2 Repository. Provide here is an super simple example CRUD application that will help you get started using the framework.

Resources

Demo

Original Blog Entry

What We'll Be Covering

In this tutorial we will be covering the following:

  • Creating a Basic App
  • Connecting to a MySQL Database
  • Creating Logic to handle CRUD of a single model
  • Handling Basic Authentication

What You'll Need

  • A Webserver setup with either Apache or Nginx.
  • A MySQL Database (5.5+)
  • Yii 2 Requirements (I'm running 5.5.x)

Disclaimers

Before we get started...

  • Yii Framework 2 is by no means "Production Ready". Don't use it in production. What is provided here is mainly used as an example of what you can do in Yii Framework 2.

  • All the code released with this blog post is released under the MIT License. Feel free to fork it and play with it.

  • The code provided is by no means "Production Ready". If you run this code in a production environment you run it at your own risk.

  • For simplicity, all commands will be from a Linux shell, and I will not be providing specific Windows commands. Adapt them as necessary for Windows.

  • This article may be reproduced and distributed under the following conditions - please don't steal my writing and claim it as your own:

    1. A link to the original article MUST be provided in the reproduction:

      Erianna

    2. My name and Copyright MUST be provided in the reproduction:

      Copyright &copy 2013 Charles R. Portwood II | Erianna

Creating Your App

When I wrote my first tutorial covering this topic Yii didn't have a basic web application yet. As of the time of writing, Yii now includes three web applications for you to test and try out. As with my last tutorial, we're going to do everything from scratch for the learning experience.

Creating Your Web Skeleton

First, create a new folder in your web directory. Inside that folder add the following folders:

/config/
/web/
    /assets/
    /css/
/assets/
/commands/
/controllers/
/migrations/
/models/
/runtime/
/views/
    /site/
    /layouts/

A couple things to notice here that are kinda important:

First, as of the time of writing, the concept of a "protected" folder is gone. Yii look a lot more like the other PHP frameworks out there now (Codeigniter, Zend, CakePHP to name a few) in terms of base folder structure. The second thing to notice is that we have 2 assets folders. The assets folder in the root of our directory is going to contain stuff to handling assets, whereas the assets folder inside of our web/ directory is going to contain our compiled assets.

Now onto some files:

The first file we want to create is yii, which is equivalent to yiic in Yii Framework 1.x. Create this file in the root of your directory, then add the following:

#!/usr/bin/env php 
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);

// fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));

require(__DIR__ . '/vendor/autoload.php');
require(__DIR__ . '/vendor/yiisoft/yii2/yii/Yii.php');

$config = require(__DIR__ . '/config/console.php');

$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);

Then, create a file called AppAsset.php in assets/:

<?php
namespace app\assets;
use yii\web\AssetBundle;

class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}

Finaly, create your index.php bootstrap file in web/index.php:

<?php

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php');

$config = require(__DIR__ . '/../config/web.php');

(new yii\web\Application($config))->run();

This last part is very important. Your web directory needs to point to web/index.php rather than index.php. Until you make this change you won't be able to see anything when you run your application. We're not at the point where you can see stuff yet, but it's important to point this out first so that we can make sure it's taken care of.

With these two files in place we can create our composer file that will download and install Yii Framework 2 for us. Create a new file in the root of our appliation called composer.json

{
        "name": "A Basic CRUD App",
        "description": "Yii 2 Basic CRUD Example",
        "keywords": ["yii", "framework", "basic", "CRUD"],
        "homepage": "http://yf2.erianna.com",
        "type": "project",
        "license": "MITC",
        "minimum-stability": "dev",
        "require": {
                "php": ">=5.4.0",
                "yiisoft/yii2": "*",
                "yiisoft/yii2-bootstrap": "*",
                "yiisoft/yii2-codeception": "*",
                "yiisoft/yii2-debug": "*",
                "yiisoft/yii2-gii": "*",
                "yiisoft/yii2-swiftmailer": "*" 
        },  
        "scripts": {
                "post-create-project-cmd": [
                        "yii\composer\Installer::setPermission"
                ]   
        },  
        "extra": {
                "writable": [
                        "runtime",
                        "web/assets"
                ],  
                "executable": [
                        "yii"
                ]   
        }   
}

Next we'll be using composer to install Yii for us. If you already have composer installed you can skip ahead.

Install Composer

Yii 2 now uses composer to download additional packages Yii uses. Before we can get started using Yii we'll need to download and install composer.

curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/bin
sudo ln -s /usr/bin/composer.phar /usr/bin/composer

Then install the composer dependencies

sudo composer self-update
composer install
Installing Yii

With the installation of composer taken care of, we can now install Yii and it's dependencies

# Install Yii Dependencies
composer install

If everything goes well, you should see a bunch of output that looks similar to the following. If you get an error, fix it and try running composer install again.

Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing ezyang/htmlpurifier (v4.5.0)
    Loading from cache

  - Installing michelf/php-markdown (1.3)
    Loading from cache

  - Installing phpspec/php-diff (dev-master 30e103d)
    Cloning 30e103d19519fe678ae64a60d77884ef3d71b28a

  - Installing yiisoft/jquery (1.10.2)
    Loading from cache

  - Installing yiisoft/yii2-composer (dev-master 96ecb97)
    Cloning 96ecb97c2083231706147f026d104d82a7202ad0

  - Installing swiftmailer/swiftmailer (dev-master f0be830)
    Cloning f0be8302f28913af7bd7df6639e5bec5b5e79257

  - Installing twbs/bootstrap (dev-master 2854c5c)
    Cloning 2854c5c3fb65b709fbf32d05faccf7a294626cca

  - Installing yiisoft/yii2 (dev-master e6ac68b)
    Cloning e6ac68b89c520befbcb4682880ac8284f1d094dd

  - Installing yiisoft/yii2-codeception (dev-master d7e6e58)
    Cloning d7e6e5806483647a5fee6462bb216e67773d9e88

  - Installing yiisoft/yii2-bootstrap (dev-master 54fedb4)
    Cloning 54fedb4c22b057b27ff088d11e6f411992956eeb

  - Installing yiisoft/yii2-debug (dev-master 97e2460)
    Cloning 97e24600932d511f419c114ef1d44e85211c47c2

  - Installing yiisoft/yii2-gii (dev-master 84bb194)
    Cloning 84bb19424561b744167636fb893701a15368d58b

  - Installing yiisoft/yii2-swiftmailer (dev-master d378f7d)
    Cloning d378f7d6d731c8130597411935d7ee05aa73897a

Writing lock file
Generating autoload files

Creating a Config file

Now that we've setup our application to be bootstrapped, we can create a config file. The config files for Yii 2 are pretty much the same as they were in Yii 1 with only a few exceptions. We'll create that file in config/web.php. Note, that I'm going to diverge a bit from the example app for simplicity.

<?php
    return array(
        'id' => 'app',

        // Preload the Debug Module
        'preload' => array(
            'debug'
        ),

        'basePath' => dirname(__DIR__),
        'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),

        // Components
        'components' => array(
            // UrlManager
            'urlManager' => array(
                'class' => 'yii\web\UrlManager',

                // Disable index.php
                'showScriptName' => false,

                // Disable r= routes
                'enablePrettyUrl' => true
            ),

            // Caching
            'cache' => array(
                'class' => 'yii\caching\FileCache'
            ),

            // UserIdentity
            'user' => array(
                'identityClass' => 'app\models\User',
            ),

            // Logging
            'log' => array(
                'traceLevel' => YII_DEBUG ? 3 : 0,
                'targets' => array(
                    array(
                        'class' => 'yii\log\FileTarget',
                        'levels' => array('error', 'warning')
                    )
                )
            ),

            // Database
            'db' => array(
                'class' => 'yii\db\Connection',
                'dsn' => 'mysql:host=localhost;dbname=yf2',
                'username' => 'yf2',
                'password' => 'yf2',
                'charset' => 'utf8'
            )
        ),

        // Modules
        'modules' => array(
            'debug' => 'yii\debug\Module',
            'gii'   => 'yii\gii\Module'
        ),

        // Extra Params if we want them
        'params' => array()
    );

At this point if we visit our webserver we'll get a lovely error message:

Not Found (#404)

Unable to resolve the request "".
The above error occurred while the Web server was processing your request.

Please contact us if you think this is a server error. Thank you.

If you get that, that means Yii 2 was able to bootstrap itself.

Creating the Database

For this application we are going to create a simple "posts" database using migrations. Before we do that though, we need to create a MySQL user and database. We can do that using the following commands:

mysql > 
CREATE USER 'yf2' IDENTIFIED BY 'yf2';
CREATE DATABASE yf2;
GRANT ALL PRIVILEGES ON yf2.* TO 'yf2' WITH GRANT OPTION;
FLUSH PRIVILEGES;

Next, we need to create a config file for our console application to run from. Create a file called console.php in config/ with the following:

<?php
return array(
    'id' => 'basic-console',
    'basePath' => dirname(__DIR__),
    'preload' => array('log'),
    'controllerPath' => dirname(__DIR__) . '/commands',
    'controllerNamespace' => 'app\commands',
    'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
    'components' => array(
        'cache' => array(
            'class' => 'yii\caching\FileCache',
        ),
        // Database
        'db' => array(
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=yf2',
            'username' => 'yf2',
            'password' => 'yf2',
            'charset' => 'utf8'
        ),

        'log' => array(
            'targets' => array(
                array(
                    'class' => 'yii\log\FileTarget',
                    'levels' => array('error', 'warning'),
                ),
            ),
        ),
    ),
    'params' => array(),
);

Migrations

With our config option in place, we can now create a migration for our database. From your command line run and type yes at the prompt.

php yii migrate/create posts

Your output should look as follows:

Yii Migration Tool (based on Yii v2.0.0-dev)

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

The migrations in Yii 2 haven't changed much since Yii 1. Replace the up() and down method with the following respectively:

public function up()
{
    return $this->createTable('posts', array(
        'id' => 'INT PRIMARY KEY AUTO_INCREMENT',
        'title' => 'VARCHAR(255)',
        'data' => 'TEXT',
        'create_time' => 'INT',
        'update_time' => 'INT'
    ));
}

public function down()
{
    return $this->dropTable('posts');
}

From the command line you can now create the database using the migrate/up command

php yii migrate/up --interactive=0

Which should give you the following output:

Yii Migration Tool (based on Yii v2.0.0-dev)

Total 1 new migration to be applied:
    m131220_164042_posts

*** applying m131220_164042_posts
    > create table posts ... done (time: 0.056s)
*** applied m131220_164042_posts (time: 0.093s)


Migrated up successfully.

migrate/down will drop the table. Feel free to bring the database up and down to explore some.

Creating the Database Model

Now that our database table has been created we can create the mode associated to our posts table. To do this we're going to use good old Gii, which has recieved a major update in terms of prettyness.

Gii

Click on the Model Generator and add the following values to the form:

Table Name: posts
Model Class: Posts

Then click preview, then generate. Yii will then write out models/Posts.php for us. The default models that Yii generates are tiny compared to that of Yii 1. There are a few changes we need to make first though.

First, we need to modify our rules() method. Let's make ID and create_time not required:

public function rules()
{
    return [
        [['title', 'data'], 'required'],
        [['data'], 'string'],
        [['create_time', 'update_time'], 'safe'],
        [['title'], 'string', 'max' => 255]
    ];
}

Next, let's add a behavior to automated the create_time and update_time timestamps. Yii thankfully provides a behavior called AutoTimestamp that we can use to automated this process.

public function behaviors()
{
    return array(
        'timestamp' => array(
            'class' => 'yii\behaviors\AutoTimestamp'
        )
    );
}

Anytime we modify a record now, the database will automatically set the appropriate timestamp.

Creating the Controller

Now that we have a basic model we can create our SiteController that will handle adding and updating things. We'll start by creating a new file called SiteController.php in controllers/ and adding the following:

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\Posts;

class SiteController extends Controller
{

}

As much as I hate namespaces in PHP, Yii 2 has made using them less painful than they were when this article was written last time. For this application we're only going to be created an index, save, and delete action for our site to use.

Handling Errors

Nobody like errors, but we'll we need to handle them someway.

public function actions()
{
    return [
        'error' => [
            'class' => 'yii\web\ErrorAction',
        ]
    ];
}

Loading Models

Before we create our actions, let's create a helper method to load our model. Those familiar with Yii should recognize it.

private function loadModel($id)
{
    $model = Posts::find($id);

    if ($model == NULL)
        throw new HttpException(404, 'Model not found.');

    return $model;
}

The one change we'll need to make to make this work is the inclusion of a new class HttpException. To include this class, add the following to your use block at the top of your controller.

use yii\web\HttpException;

Deleting Records

Deleting records is pretty simple, so lets create that action first.

public function actionDelete($id=NULL)
{
    $model = $this->loadModel($id);

    if (!$model->delete())
        Yii::$app->session->setFlash('error', 'Unable to delete model');

    $this->redirect($this->createUrl('site/index'));
}

There's nothing too surprising here, we delete the model and redirect to the index page. And if we encounter an error we set a pretty flash message which we will display in our views.

Viewing Records

Viewing records is also pretty simple, just load all the models and pass them to the view for display. Notice that $this->render() now just returns a value rather than outputting it. If you want to display content on the page you'll need to echo it.

public function actionIndex()
{
    $models = Posts::find()->all();
    echo $this->render('index', array('models' => $models));
}

Creating/Updating Records

I like having creation/updating in the same method for an application as simple as this. We can create a hybrid method as follows:

public function actionSave($id=NULL)
{
    if ($id == NULL)
        $model = new Posts;
    else
        $model = $this->loadModel($id);

    if (isset($_POST['Posts']))
    {
        $model->load($_POST);

        if ($model->save())
        {
            Yii::$app->session->setFlash('success', 'Model has been saved');
            $this->redirect($this->createUrl('site/save', array('id' => $model->id)));
        }
        else
            Yii::$app->session->setFlash('error', 'Model could not be saved');
    }

    echo $this->render('save', array('model' => $model));
}

Simply put, if we are given an ID, try loading it with our loadModel() method (Which will automatically throw an error for us if one is not found). If we aren't given an ID, we'll assume the user wants to create a new record. Then, if the user submits any data we try to save it, and update the user when we make that attempt.

For now, that takes care of our controllers. (Pretty easy, huh?). We'll come back to our controller a little later when to add authentication. For now let's move onto creating our views.

Views

Now on to creating views. There's 3 views we'll need to create, our layout, the index view, and our save view.

For now, just touch (create the files) views/site/index.php and views/site/save.php, then create views/layouts/main.php.

Layout

Layouts in Yii 2 are a bit more complex than they were in Yii 1, but it's nothing that we can't handle.

First, we need to include all the view helpers we're going to use (including our asset manager). We're going to use Twitter Bootstrap to make things pretty for now.

<?php

use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use app\assets\AppAsset;

/**
 * @var \yii\web\View $this
 * @var string $content
 */
AppAsset::register($this);
?>

I'm not really going to go into AppAsset at this time. For now, all you need to know is that it's taking care of all of our assets for us.

Next, we're going to add all of our markup.

<?php $this->beginPage() ?>
<!DOCTYPE html >
<html >
<head>
    <title><?php echo  Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head >
<body style="margin-top: 100px;"; >
<?php $this->beginBody() ?>
    <?php
        NavBar::begin([
            'brandLabel' => 'Home',
            'brandUrl' => Yii::$app->homeUrl,
            'options' => [
                'class' => 'navbar-inverse navbar-fixed-top',
            ],
        ]);
        NavBar::end();
    ?>

    <div class="container">
        <?php if (Yii::$app->session->hasFlash('success')): ?>
            <div class="alert alert-success">
                <?php echo Yii::$app->session->getFlash('success'); ?>
            </div>
        <?php elseif (Yii::$app->session->hasFlash('error')): ?>
            <div class="alert alert-danger">
                <?php echo Yii::$app->session->getFlash('error'); ?>
            </div>
        <?php endif; ?>

        <?php echo  $content ?>
    </div>

<?php $this->endBody() ?>
</body >
</htm l>
<?php $this->endPage() ?>

A few new controllers methods here - but nothing too surprising. Basically the begin/end Body/Page methods are placeholders for any css and javascript Yii will inject into our templates. Most likely these were moved outside of the renderer to improve performance.

That last thing to note is that we're going to place our flash messages in our layout and display them if they were provided

With our layout in place, we can now visit http://localhost in our browser and see some stuff!

Base Template

With our layout done, let's take a quick look at the fancy new profiler at the bottom.

Profiling on Steroids

Remember how we added the debug module to our config/web.php file? This is what we get in return. Rather than just dumping everything to the screen, Yii 2 is actually profiling in the background and just giving us a "at a glance" details, which is pretty neat.

What's even more neat is when you click on any of the items in the bar you get an amazing profiler that has all sorts of using information such as your current config, request parameters, profiling, and any logs that were written for that request. The profiler alone (at least in my opinion) is one of the coolest new features coming in Yii 2.

Index File

With our layout in place, we can now create our index view file. We're going to use the same code from my previous article with just a few alterations:

<?php use yii\helpers\Html; ?>

<?php echo Html::a('Create New Post', array('site/save'), array('class' => 'btn btn-primary pull-right')); ?>
<div class="clearfix"></div>
<hr />
<table class="table table-striped table-hover">
    <tr>
        <td>#</td>
        <td>Title</td>
        <td>Created</td>
        <td>Updated</td>
        <td>Options</td>
    </tr>
    <?php foreach ($models as $post): ?>
        <tr>
            <td>
                <?php echo Html::a($post->id, array('site/save', 'id'=>$post->id)); ?>
            </td>
            <td><?php echo Html::a($post->title, array('site/save', 'id'=>$post->id)); ?></td>
            <td><?php echo date('m/d/Y H:i', $post->create_time); ?></td>
            <td><?php echo date('m/d/Y H:i', $post->update_time); ?></td>
            <td>
                <?php echo Html::a('update', array('site/save', 'id'=>$post->id)); ?>
                <?php echo Html::a('delete', array('site/delete', 'id'=>$post->id)); ?>
            </td>
        </tr>
    <?php endforeach; ?>
</table>

There's nothing odd here, just note that you have to include the view helper (Yii doesn't automatically include it from the layout).

Creating & Updated Records

Create a new file called save.php in views/site/ and add our form:

<?php 
use yii\helpers\Html;
use yii\widgets\ActiveForm; 
?>

<?php $form = ActiveForm::begin(array(
    'options' => array('class' => 'form-horizontal', 'role' => 'form'),
)); ?>
    <div class="form-group">
        <?php echo $form->field($model, 'title')->textInput(array('class' => 'form-control')); ?>
    </div>
    <div class="form-group">
        <?php echo $form->field($model, 'data')->textArea(array('class' => 'form-control')); ?>
    </div>
    <?php echo Html::submitButton('Submit', array('class' => 'btn btn-primary pull-right')); ?>
<?php ActiveForm::end(); ?>

$form has changed a bit, but it's more or less the same from the last time we went through this. The biggest change is the use of ActiveForm.


At this point we've create a pretty simple system for displaying and updating data. If you want, try making a "view" view that will allow you to see the data outside of an update view.

Handling Authentication

With the core of our application completed, let's now add some basic authentication. For this appliance we're going to require authentication for users editing and deleting content, but not for viewing.

This first thing we need for authentication is a Users model which we're just going to take from the Yii 2 Basic application. Create a new file called User.php in models/ and add the following:

<?php

namespace app\models;

class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    public $id;
    public $username;
    public $password;
    public $authKey;

    private static $users = [
        '100' => [
            'id' => '100',
            'username' => 'admin',
            'password' => 'admin',
            'authKey' => 'test100key',
        ],
        '101' => [
            'id' => '101',
            'username' => 'demo',
            'password' => 'demo',
            'authKey' => 'test101key',
        ],
    ];

    public static function findIdentity($id)
    {
        return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
    }

    public static function findByUsername($username)
    {
        foreach (self::$users as $user) {
            if (strcasecmp($user['username'], $username) === 0) {
                return new static($user);
            }
        }
        return null;
    }

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

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

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

    public function validatePassword($password)
    {
        return $this->password === $password;
    }
}

Okay, so there's a lot of new things here, let's break them down one by one.

First, we need to declare our namespace. Since we're creating a model, this is an application model.

<?php
namespace app\models;

Next, we extend Yii's base object class and implement a few methods defined in IdentityInterface and declare some properties our model will work with.

class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    public $id;
    public $username;
    public $password;
    public $authKey;
}

What we ultimatly will pass around will be a static object. The newest thing for our UserIdentity is the use of yii\web\IdentityInterface, which contains four methods we'll need to implement, getAuthKey(), validateAuthKey(), getId(), and findIdentity().

We also have this $authKey thing that we'll get to in a bit.

For this application we'll only be using a file based user lists. We can defined that as:

private static $users = [
    '100' => [
        'id' => '100',
        'username' => 'admin',
        'password' => 'admin',
        'authKey' => 'test100key',
    ],
    '101' => [
        'id' => '101',
        'username' => 'demo',
        'password' => 'demo',
        'authKey' => 'test101key',
    ],
];

Next, we have findIdentity(), which returns a static instance of User of an item from the array above. We're going to call this method after we validate the user's credentials,

public static function findIdentity($id)
{
    return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
}

Next we have findByUsername() which is just a simple helper for finding a username in our flat file database.

public static function findByUsername($username)
{
    foreach (self::$users as $user) {
        if (strcasecmp($user['username'], $username) === 0) {
            return new static($user);
        }
    }
    return null;
}

The remaining methods are just validators on the static instance we'll get back from findIdentity(). With the exception of validatePassword() we have to implement these methods

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

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

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

public function validatePassword($password)
{
    return $this->password === $password;
}

These key values are for automatic logins. Rather than having to pass the username and password for reauthentication with autologin enabled, we an pass a key which we'll consider valid. Each user should have a unix authKey, and should be persisted in a database when it's created. Additionally, the keyspace of this key should be sufficiently large enought to thward identity attacks.

Login Model

Now that our Identity is setup, we can create a LoginModel for authentication this data. Begin by creating models/LoginForm.php and adding the following:

<?php
namespace app\models;
use Yii;
use yii\base\Model;

class LoginForm extends Model
{
    public $username;
    public $password;
    public $rememberMe = true;
    private $_user = false;
}

Then add some validation rules:

public function rules()
{
    return [
        // username and password are both required
        [['username', 'password'], 'required'],
        // password is validated by validatePassword()
        ['password', 'validatePassword'],
        // rememberMe must be a boolean value
        ['rememberMe', 'boolean'],
    ];
}

There's three methods we'll want to define, validatePassword() (Since we declared it as a password validator), login() (So we can login), and getUser(), which will retrieve our user identity for us.

We can start by defining getUser(), which will call our identities findByUsername() method and return it if it is found.

private function getUser()
{
    if ($this->_user === false) {
        $this->_user = User::findByUsername($this->username);
    }
    return $this->_user;
}

Next we'll define our validator, which will retrieve our user via getUser(), and validate the password against our identities validatePassword() method.

public function validatePassword()
{
    $user = $this->getUser();
    if (!$user || !$user->validatePassword($this->password)) {
        $this->addError('password', 'Incorrect username or password.');
    }
}

Finally, we have our login() method which will run our validators and create our session. The biggest thing to note here is that our Yii::$app->user->{attribute}'s are now set via Yii::$app->user->login($attributes). Anything in our getUser() method will be populated into our session, which is a nice improvement over Yii 1.

public function login()
{
    if ($this->validate()) {
        return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
    } else {
        return false;
    }
}

Updating our Controller

With our models taken care of we can now move back to our controller and create login/logout methods.

First, we need to include our LoginForm model. We can do that by adding a use statement to the top of our controller:

use app\models\LoginForm;

Logout hasn't changed much:

public function actionLogout()
{
    Yii::$app->user->logout();
    return $this->goHome();
}

And login is pretty much the same as Yii 1 as well. Notice that we're going to be using a new layout called signin.

Also, Yii 2 provides these neat goHome() and goBack() methods for redirecting.

public function actionLogin()
{
    $this->layout = 'signin';

    if (!\Yii::$app->user->isGuest) {
        $this->goHome();
    }

    $model = new LoginForm();
    if ($model->load($_POST) && $model->login()) {
        return $this->goBack();
    } else {
        return $this->render('login', [
            'model' => $model,
        ]);
    }
}
Signin Layout

The layout for authentication is going to be the same as our main layout sans the header:

<?php
use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
use app\assets\AppAsset;

AppAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html >
<html lang="<?= Yii::$app->language ?>">
<head >
    <meta charset="<?= Yii::$app->charset ?>"/>
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head >
<body class="signin">
<?php $this->beginBody() ?>
    <div class="row">
        <div class="container">
            <?php echo $content; ?>
        </div>
    </div>
<?php $this->endBody() ?>
</body >
</htm l>
<?php $this->endPage() ?>

Since twitter bootstrap already provides CSS for a nice looking login page, let's add it to web/assets/css/signing.css:

body.signin {
  padding-top: 40px;
  padding-bottom: 40px;
  background-color: #eee;
}

.signin .form-signin {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}
.signin .form-signin .form-signin-heading,
.signin .form-signin .checkbox {
  margin-bottom: 10px;
}
.signin .form-signin .checkbox {
  font-weight: normal;
}
.signin .form-signin .form-control {
  position: relative;
  font-size: 16px;
  height: auto;
  padding: 10px;
  -webkit-box-sizing: border-box;
     -moz-box-sizing: border-box;
          box-sizing: border-box;
}
.signin .form-signin .form-control:focus {
  z-index: 2;
}
.signin .form-signin input[type="text"] {
  margin-bottom: -1px;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
.signin .form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

We'll also need to include our CSS in AppAsset.php, which can be done by adding a new public property to the class:

public $css = [
    'css/signin.css',
];
Login Form

Now for the login form. Create a new file called views/site/login.php and add the following. The form should be pretty straightforward.

<?php 
use yii\helpers\Html;
use yii\widgets\ActiveForm; 
?>
<?php $form = ActiveForm::begin(array(
    'options' => array('class' => 'form-signin', 'role' => 'form'),
)); ?>
    <h2 class="form-signin-heading">Please sign in</h2>
    <div class="form-group">
        <?php echo $form->field($model, 'username')->textInput(array('class' => 'form-control')); ?>
    </div>
    <div class="form-group">
        <?php echo $form->field($model, 'password')->passwordInput(array('class' => 'form-control')); ?>
    </div>
    <?php echo Html::submitButton('Submit', array('class' => 'btn btn-primary pull-right')); ?>
<?php ActiveForm::end(); ?>

Now if you visit http://localhost/site/login you'll be shown a working authentication form.

Requiring Authentication

With our authentication form working we can now make it required for certain views. Let's allow the index action, but require authentication for save and delete.

In Yii 2 we can handle this by attaching a AccessControl behavior as follows:

public function behaviors()
{
    return [
        'access' => [
            'class' => 'yii\web\AccessControl',
            'only' => ['save', 'delete', 'logout'],
            'rules' => [
                [
                    'actions' => ['index', 'save', 'delete'],
                    'allow' => true,
                    'roles' => ['@'],
                ],
            ],
        ]
    ];
}

The only element means we should only require authentication for those actions, and for the actions that it applies to we should allow the user only if they are authenticated (@).


Concluding Thoughts

Yii2 has come along way since I first posted this. I hope that his update helps new users wanted to explore Yii 2 better understand what to expect from the framework and to get a feel for what is to come.

Resources

Demo

Github

Original Blog Entry

For more tutorials, guides, source code, and information visit my blog at https://www.erianna.com.

Total 7 comments

#17834 report it
rdewilde at 2014/07/27 02:12pm
Outdated classname accesscontrol
yii\web\AccessControl

should now be

\yii\filters\AccessControl::className()

http://www.yiiframework.com/doc-2.0/yii-filters-accesscontrol.html

#17699 report it
ThePr0f3550r at 2014/07/15 09:16pm
Good job

Good job.. please explain about RBAC.. for example diferent access for Admin, Author, Member, Guest

#17321 report it
Charles R. Portwood II at 2014/05/23 08:40am
Database Authentication

@mbala,

This guide only covers simple authentication from a flat file inline database.

private static $users = [ '100' => [ 'id' => '100', 'username' => 'admin', 'password' => 'admin', 'authKey' => 'test100key', ], '101' => [ 'id' => '101', 'username' => 'demo', 'password' => 'demo', 'authKey' => 'test101key', ], ];

If you wanted use a users table within the database rather than using the flat file...

  1. Create a new table in your db called users with the following schema (at minimum)

    ID PK username STRING password STRING authKey STRING

  2. Using Gii, create a users model

  3. Update your findByUsername and findByIdentity method in your User class to do a find search using the provided credentials and to retrieve the user information rather than retrieving it from the inline array.
#17311 report it
mbala at 2014/05/21 12:57pm
How to check the login details from database

Hi, Your code was working fine for me. Now i need to login using database user table. How to handle this. If you can, explain with login form and user model.

#15893 report it
Charles R. Portwood II at 2013/12/27 08:36am
Gii

@XaRz,

Your Apache server needs to point to web/, not the base of your project. web/ shouldn't be in the URL. Make that change, then verify modrewrite and your htaccess file is in place and rewriting and you should be good.

#15892 report it
XaRz at 2013/12/27 05:01am
gii in your tutorial shows me the boostraped yii

Hello,

I'm trying to follow your instructions but in the step to create the models with gii I'm reciving the same 404 message you shows above (when you says that yii shows bootstrapped itself)

I'm trying to access to gii through: http://dev.yiiblog.cat:8888/web/index.php?r=gii (MAMP PRO env!)

No way to access gii. Till then all steps done well including the migrations.

Any hints?

#15836 report it
Charles R. Portwood II at 2013/12/20 07:21pm
Updated

Content has been updated to reflect Yii 2 as of 12/20/2013

Leave a comment

Please to leave your comment.

Write new article