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

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

    1. 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.phpinassets/`:

<?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.phprather thanindex.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.phpinconfig/` 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()anddown` 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.

### 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](http://yf2.erianna.com/1.png)

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](http://yf2.erianna.com/2.png)

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](http://yf2.erianna.com)

[Github](http://github.com/charlesportwoodii/yf2-demo)

[Original Blog Entry](https://www.erianna.com/getting-starting-with-yii-framework-2)

For more tutorials, guides, source code, and information visit my blog at [https://www.erianna.com](https://www.erianna.com).
7 6
21 followers
Viewed: 303 760 times
Version: 2.0
Category: Tutorials
Last updated by: Vivek
Created on: May 4, 2013
Last updated: 9 years ago
Update Article

Revisions

View all history

Related Articles