Revision #4 has been created by Charles R. Portwood II on Dec 21, 2013, 12:20:13 AM with the memo:
Updated to match Yii 2 (12/20/2013)
« previous (#3) next (#5) »
Changes
Title
changed
Creating a Simple CRUD App With Yii2 (Revised 12/20/2013)
Category
unchanged
Tutorials
Yii version
unchanged
Tags
unchanged
yii2, tutorial, howto
Content
changed
# Getting Started With Yii Framework 2. A Basic Tutorial
### Disclaimer
This guide is meant to help you started with Yii2. Yii2 is by no means "production" ready. I do not recommend using this in production.
------------
### Edit
This guide has been updated to reflect some changes that should import the quality of this post. Namely:
1) Getting ActiveRecord to autoincriment the database
2) Changing the charset of utf8.
3) Removal of model() method in Post Model
4) Update findallMethod
5) Added Flash Messages instead of 404 errors.
6) Removal of XSS Injection Vector <-- FML
------------
Today Yii Framework made the announcement that Yii2 was now available for a public preview. A lot has changed from Yii1 to Yii2,
This tutorial will go over making a simple blog site in Yii2. For this guide, we'll be getting and installing Yii2, creating a base app, connecting to a database, and configuring logic to create, updated, read, and delete posts.
For this tutorial, here's what you'll need:
* A webserver, either Apache or Nginx. For this guide I'll be using Nginx. The conversion to Apache should be trivial however, so don't worry if you don't have an Nginx server lying around.
* A database to connect our app to. I'll be using MySQL 5.5
* Basic PHP knowledge. I'll try to keep it as simple as possible, but the more PHP you know, the easier it will be to follow along.
* Basic Knowledge of either Yii or MVC. If you don't have any experience with MVC, I recommend you [read up on MVC fundamentals](http://www.yiiframework.com/doc/guide/1.1/en/basics.mvc). You _can_ follow this guide without them, however things will make more sense if you have a concept of what MVC is.
So, lets get started!
For this tutorial I'm going to assume that you already have your webserver setup. For this guide, I'm going to be using the following directories and urls:
* /var/www/yii2 for my DocumentRoot
* yii2.erianna.com for my hosting url
Additionally, by the end of this tutorial you will be able to view a demo of the app we've made at [yii2.erianna.com](yii2.erianna.com).
### Downloading Yii2
Download a copy of Yii2 from Github either by cloning the repository or by downloading a tar archive.
git clone git@github.com:yiisoft/yii2.git /dir/to/yii2
or
wget https://github.com/yiisoft/yii2/archive/master.zip
unzip master.zip /dir/to/yii2
Once you've extracted Yii2, navigate to __/dir/to/yii2/framework__
cd /dir/to/yii2/framework
And run the following commands to setup your first webapp, providing yes to the first prompt.
php yiic.php app/create /var/www/yii2
yes
This is the equivalent of creating a new webapp in Yii 1.x. Now navigate to /var/www/yii2. Inside this folder you'll see one file and one folder.
~~~
$ ls -l
total 8
-rwxrwxrwx 1 user www-data 265 May 4 09:30 index.php
drwxrwsr-x 5 user www-data 4096 May 4 09:07 protected
~~~
Before we can get our site up and running, we'll need to make some modifications to our index.php file. In my opinion, there are some questionable design choices. Hopefully these will be fixed before Yii2's final release to make it more user friendly to get setup.
Change your index.php to look like this.
```php
<?php
define('YII_DEBUG', true);
// Change this to your actual Yii path
require '/path/to/yii2/framework/yii.php';
// Change __DIR__ to __FILE__ so we can load our config file
$config = require dirname(__FILE__).'/protected/config/main.php';
$config['basePath'] = dirname(__FILE__).'/protected';
$app = new \yii\web\Application($config);
$app->run();
```
Lets break down the changes we made:
```php
// Change this to your actual Yii path
require '/path/to/yii2/framework/yii.php';
```
First, we need to change our "require" path to point to to where our framework/yii.php is actually located at. By default, this makes the assuming it is in the current directory, It's possible it might be, but it needs to know exactly where Yii2 is located at.
```php
$config = require dirname(__FILE__).'/protected/config/main.php';
$config['basePath'] = dirname(__FILE__).'/protected';
```
Next, we updated our config path to use <strong>__FILE__</strong> instead of <strong>__DIR__</strong>. We're making this change so our webapp can actually load.
--------------------
Before we continue, it's important to notice there's something new in Yii" __Name spaces__
```php
$app = new \yii\web\Application($config);
```
The point of name spaces is to encapsulate code in logical units to prevent collision of multiple code bases. So you have two classes, both named __Foo__ and that both have the method __Bar__, assuming they are both name spaces you can call both of them independently of each other as follows, without any collision of classes.
```php
$foo = new \namespace\Foo;
$foo2 = new \namespace2\Foo;
```
Name spaces are an easy way to prevent collision of code. I'd recommend you'd [read up on them](http://www.php.net/manual/en/language.namespaces.rationale.php), since Yii2 has been name spaced.
---------------
And like that, you've just created your fist web app! Navigate to where yii2 is located at, and you should see the following page.
<center>
<img src="https://www.erianna.com/uploads/c4ca4238a0b923820dcc509a6f75849b.png" style="max-width: 700px"/>
<span style="font-weight:bold; font-size: 12px;">Your First Yii2 App!</span>
<br />
<br />
<br />
</center>
Unlike Yii 1.x's skeleton app, the base skeleton app for Yii2 isn't that exciting yet. Lets make it do a little more.
First, open up your __/protected/views/layout/main.php__ file, then replace it with the following code:
```php
<?php use yii\helpers\Html as Html; ?>
<!doctype html>
<html lang="<?php \Yii::$app->language?>">
<head>
<meta charset="utf-8" />
<title><?php echo Html::encode(\Yii::$app->name); ?></title>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="navbar navbar-inverse">
<div class="container">
<div class="navbar-inner">
<a class="brand" href="/"><?php echo Html::encode(\Yii::$app->name); ?></a>
</div>
</div>
</div>
<div class="content">
<?php echo $content?>
</div>
</div>
</body>
</html>
```
Then refresh the page. See? Isn't everything prettier with Twitter Bootstrap? Again, not much has changed from Yii1 to Yii2. You still have $content being the variable you use for displaying content in views. __Yii::app()__ has changed to be __Yii::$app__ however. Again, everything in Yii2 has been name spaced, so it's important to remember to access everything by their new name space instead of just calling the raw class.
Now lets do some real coding!
### Connecting To Your Database
For this app, we'll just have a simple __Posts__ table containing our blog posts.
#### Creating the Database Table
Login to MySQL, and create a user and database both named yii2. Then run the following command to update the db structure for yii2.
~~~
DROP TABLE IF EXISTS `posts`;
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_bin NOT NULL,
`content` text COLLATE utf8_bin NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;
INSERT INTO `yii2`.`posts` (`id`, `title`, `content`, `created`, `updated`) VALUES ('1', 'Example Title', 'New Post', NOW(), NOW());
~~~
#### Updating Your Config
Then, navigate to __/var/www/yii2/protected/__ and open up __config.php__ in your favorite editor and replace it with the following.
```php
<?php
return array(
'id' => 'webapp',
'name' => 'My Web Application',
'components' => array(
// uncomment the following to use a MySQL database
'db' => array(
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhostt;dbname=yii2',
'username' => 'yii2',
'password' => '<password>',
),
'cache' => array(
'class' => 'yii\caching\DummyCache',
),
),
);
```
If you're familiar with Yii2, this is a _massive_ improvement over the hideous config files that were generated in Yii1. While the same structure is there, this is the only thing that is needed to get your database up and running.
#### Creating a Post Model
Create a new folder called __models__ under protected, and then created a new file call __Post.php__ in __models__ and add the following code to it.
```php
<?php
namespace app\models;
class Post extends \yii\db\ActiveRecord
{
/**
* @return string the associated database table name
*/
public static function tableName()
{
return 'posts';
}
/**
* @return array primary key of the table
**/
public static function primaryKey()
{
return array('id');
}
/**
* @return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'title' => 'Title',
'content' => 'Content',
'created' => 'Created',
'updated' => 'Updated',
);
}
}
```
If you're familiar with Yii1, the only thing that has really changed in ActiveRecord (at least in this example) is that the functions __primaryKey__, and __tableName__ are now static methods. Everything else is basically the same. For the most part, ActiveRecord has remained unchanged.
The most important part of this class is the inclusion of the name space __app\models__. This tells Yii where we can reference this file at.
Unlike Yii1, where you can just call the class name, Yii2 uses a different type of auto loaded which requires you to explicitly define what classes you intent on using. While this might make development a little slower (Trying to remember to include \yii\framework\web\Html can get old really fast instead of just calling CHtml), it should make Yii2 significantly faster. Since the autoloader won't have to search through the entire framework just to get one class. At least in theory.
### CRUD!
Now that we've name spaced our Post model, we can get to working creating our basic CRUD app.
#### Viewing Everything
First, lets start by updating our index action so that we can view everything. I like to be able to view everything from my index action, so lets start there. Open up __controllers/SiteController.php__ and update your index action so it looks as follows:
```php
public function actionIndex()
{
$data = Post::find()->all();
echo $this->render('index', array(
'data' => $data
));
}
```
A couple of things to notice here. First, __::model()->__ is gone. Raw model data from ActiveRecord and Model can now be accessed directly by calling the method you want information on. So $post->find()->all(). While I am personally pretty fond of Post::model()->findAll(), the new way of accessing data pretty standard and is easier to read.
Secondly, findAll has been replaced by find()->all(). All find methods now stem either from find() or findBySql().
Finally, $this->render() now requires an echo in front of it. Personally, I hate this. It feels _very_ CakePHP ish, and is in my opinion redundant. The idea behind this however is that stuff you want to be rendered to the screen should be echoed, otherwise it is simply available as a $variable for you to manipulate. Personally, I prefer the older way of rendering to a variable (passing a function parameter to the render method), but maybe it will grow on me.
Now refresh the page...
If you're familiar with namespaces, your probably screaming at me right now asking me why I didn't include the Post model. If you're not familiar with name spaces, you're probably confuses as to why your getting an error. The reason is simple. _You have to remember your name spaces in Yii2__. Anything you want to use, you have to explicitly define unless it already has been defined.
Add the following line to top of _SiteController_. Then refresh the page.
```php
use app\models\Post;
```
Now lets add some markup to display our posts. Open up __protected/views/site/index.php__ and replace the content with the following:
```php
<?php use yii\helpers\Html; ?>
<?php echo Html::a('Create New Post', array('site/create'), 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 ($data as $post): ?>
<tr>
<td>
<?php echo Html::a($post->id, array('site/read', 'id'=>$post->id)); ?>
</td>
<td><?php echo Html::a($post->title, array('site/read', 'id'=>$post->id)); ?></td>
<td><?php echo $post->created; ?></td>
<td><?php echo $post->updated; ?></td>
<td>
<?php echo Html::a(NULL, array('site/update', 'id'=>$post->id), array('class'=>'icon icon-edit')); ?>
<?php echo Html::a(NULL, array('site/delete', 'id'=>$post->id), array('class'=>'icon icon-trash')); ?>
</td>
</tr>
<?php endforeach; ?>
</table>
```
Hmmm, looks different doesn't it! CHtml::link() is gone, and has been replaced by a helper name space called Html. Fortunately, the structure from CHtml::link to Html::a hasn't changed at all. So it's simply a matter of filling in the parameters.
#### Read
Reading is easy, so lets take care of that next. Create a new method in SiteController with the following definition:
```php
public function actionRead($id=NULL)
{
echo 'HelloWorld';
}
```
Now, if you navigate to ?r=site/read&id=1. You should see __HelloWorld__ being printed to the screen. See it? Good. That means our method is being triggered. Now lets configure it so that we can get some data from our database.
First, lets add HttpException to our SiteController so that we can throw HttpExceptions if a post isn't found.
```php
use \yii\base\HttpException;
```
Then, lets create our read action
```php
public function actionRead($id=NULL)
{
if ($id === NULL)
{
Yii::$app->session->setFlash('error', 'A post with that id does not exist');
Yii::$app->getResponse()->redirect(array('site/index'));
}
$post = Post::find($id);
if ($post === NULL)
{
Yii::$app->session->setFlash('error', 'A post with that id does not exist');
Yii::$app->getResponse()->redirect(array('site/index'));
}
echo $this->render('read', array(
'post' => $post
));
}
```
Just for clarity, HttpException is essentially CHttpException. All we're doing is making querying the database for a post if an id of $id, and rendering it. If the post isn't found, or an id isn't provided then we're throwing an HttpException.
Next, we need to create a new file __protected/views/site/read.php__, and add the following code to it to display our post.
```php
<?php use yii\helpers\Html; ?>
<div class="pull-right btn-group">
<?php echo Html::a('Update', array('site/update', 'id' => $post->id), array('class' => 'btn btn-primary')); ?>
<?php echo Html::a('Delete', array('site/delete', 'id' => $post->id), array('class' => 'btn btn-danger')); ?>
</div>
<h1><?php echo $post->title; ?></h1>
<p><?php echo $post->content; ?></p>
<hr />
<time>Created On: <?php echo $post->created; ?></time><br />
<time>Updated On: <?php echo $post->updated; ?></time>
```
Now, on our index page, click on "Example Post". Tada! You can now view posts for your blog!
#### Delete
Deleting posts is also very simple, so we'll do that next. Create a new method with the following definition:
```php
public function actionDelete($id=NULL)
{
}
```
For this method, we're going to be a little more complex. We're going to redirect the user back to the homepage after we've successfully deleted their post. Lets get started.
First, lets define our method
```php
public function actionDelete($id=NULL)
{
if ($id === NULL)
{
Yii::$app->session->setFlash('error', 'A post with that id does not exist');
Yii::$app->getResponse()->redirect(array('site/index'));
}
$post = Post::find($id);
if ($post === NULL)
{
Yii::$app->session->setFlash('error', 'A post with that id does not exist');
Yii::$app->getResponse()->redirect(array('site/index'));
}
$post->delete();
Yii::$app->session->setFlash('success', 'Your post has been successfully deleted');
Yii::$app->getResponse()->redirect(array('site/index'));
}
```
A couple things to note with Yii2. First, redirecting is now done through __Yii::$app->getResponse->redirect()__ instead of __$this->redirect()__. While this makes sense from code organization perspective, it's a pain to type out. Additionally, it also gives the feeling that $app is severely overloaded. While a pain to type, it's maintained the same method definition from Yii1.
Secondly, setFlash is now accessed through $app instead of app(). You should be getting the hange of that by now though. =)
Now that we've handled deleting, lets go back to our __protected/views/site/index.php__ file and catch those flash message we sent.
Just add the following below the first <hr /> tag
```php
<?php if(Yii::$app->session->hasFlash('error')): ?>
<div class="alert alert-error">
<?php echo Yii::$app->session->getFlash('error'); ?>
</div>
<?php endif; ?>
<?php if(Yii::$app->session->hasFlash('success')): ?>
<div class="alert alert-success">
<?php echo Yii::$app->session->getFlash('success'); ?>
</div>
<?php endif; ?>
```
Now try deleting "Example Post". Pretty neat huh? You're getting the idea of Yii::$app now, right?
#### Create
Now lets get to the fun stuff, creating new entries in our blog. We're going to need a couple of things to post creation. First, we're going to be use ActiveForm to handle the form itself. Secondly we're going to catch catching and validating $_POST data. And finally we're going to be saving it to the database for storage. Lets get started.
First, we'll need to create a view for our form. Start by creating a file __protected/views/site/create.php__. Since we'll be using a widget in our view, you'll also need to create a folder "assets" in the root of our web app and make it writable by your web server. Chmod 755 usually does the trick. Then add the following function definition to SiteController.
```php
public function actionCreate()
{
$model = new Post;
if ($this->populate($_POST, $model) && $model->save())
Yii::$app->response->redirect(array('site/read', 'id' => $model->id));
echo $this->render('create', array(
'model' => $model
));
}
```
All that this does, is check if the model we're inserting is a new record, and if it is, to get the highest id in the database and add one to it, and use that for our id. Notice the new method "populate".
I trid a bunch of different combinations (NULL, 0, _ for $model->id, but for some reason ActiveRecord refused to save the model with anything but 0. I have no idea why it isn't working yet).
Now that that has been settled out, add the view for our create file.
```php
<?php use yii\helpers\Html; ?>
<?php $form = $this->beginWidget('yii\widgets\ActiveForm', array(
'options' => array('class' => 'form-horizontal'),
)); ?>
<?php echo $form->field($model, 'title')->textInput(array('class' => 'span8')); ?>
<?php echo $form->field($model, 'content')->textArea(array('class' => 'span8')); ?>
<div class="form-actions">
<?php echo Html::submitButton('Submit', null, null, array('class' => 'btn btn-primary')); ?>
</div>
<?php $this->endWidget(); ?>
```
And there you go, you've now saved your model. But things are a little weird wouldn't you agree? For instance, why are our created and updated times all 0's? What happens if we input a blank form?
Lets fix those two issues before continuing. First, we need to open up our Post model, and add the following method:
```php
public function rules()
{
return array(
array('title, content', 'required'),
);
}
```
This method makes the title and content field required. Now when you attempt to save the model, you'll get an error if either of those fields are blank. And since we're using bootstrap, it's pretty easy to see _what_ the error was. Give it a try!
Next, we're going to auto populate our created and updated times.
First, we're going to add another __use__ line to the top of our model.
```php
use \yii\db\Expression;
```
Second, we're going to update our beforeSave method to add these automatically for us.
Inside our if ($this->isNewRecord) block, add the following line.
```php
$this->created = new Expression('NOW()');
```
Then, before return parent::beforeSave($insert) add:
```php
$this->updated = new Expression('NOW()');
```
Your final method definition should look like this:
```php
public function beforeSave($insert)
{
if ($this->isNewRecord) {
$this->created = new Expression('NOW()');
}
$this->updated = new Expression('NOW()');
return parent::beforeSave($insert);
}
```
Now try saving again. Our model now has validation on both the title, and content fields, and will automatically update the created and update time for you. Now lets do updating.
#### Update
Our update action is going to be nearly identical to our create action. The only difference between the two is how we determine what model we're going to use.
In our create action, we used.
```php
$model = new Post;
```
In our update action, we're going to use.
```php
$model = Post::find($id);
```
I like HTTPExceptions for stuff when it isn't found. That being said, you should probably be nice to the user and just warn them with a soft flash message instead of a hard http exception that makes it look like your site exploded.
```php
public function actionUpdate($id=NULL)
{
if ($id === NULL)
{
Yii::$app->session->setFlash('error', 'A post with that id does not exist');
Yii::$app->getResponse()->redirect(array('site/index'));
}
$model = Post::find($id);
if ($model === NULL)
{
Yii::$app->session->setFlash('error', 'A post with that id does not exist');
Yii::$app->getResponse()->redirect(array('site/index'));
}
if ($this->populate($_POST, $model) && $model->save())
Yii::$app->response->redirect(array('site/read', 'id' => $model->id));
echo $this->render('create', array(
'model' => $model
));
}
```
Notice anything interesting? We're still using the create view in our update action because _they are exactly the same_. Cool huh?
### Sanitizing Data Input
So you have everything setup now. Easy huh? But what happens if you throw the following into your title.
```php
<script type="text/javascript">alert("Hello!");</script>
```
If you're like me, you probably assumed ActiveRecord would sanitize that for you. I know I certainly did. Bad news folks, it doesn't. _Escape and sanitize your stuff. There are evil people about there who want to ruin you because you forgot to sanitize your user input._
So, anywhere that you're outputting user inputted data, escape it using __Html::encode()__. To provide an example, our __protected/views/site/read.php__ should now have output that looks as follows:
```php
<h1><?php echo Html::encode($post->title); ?></h1>
<p><?php echo Html::encode($post->content); ?></p>
```
Now when you visit that page, and some evil person has added a script tag in your form all that is seen is sanatized input.
### Concluding Thoughts
Well there you have it. In a couple of hours you've gone from knowing nothing about Yii Framework 2 to having a very simple CRUD application. Using this knowledge you can easily scale your application to support having users, having authentication for the views, adding additional tables to your database, and even adding more powerful features.
Yii2 is _very_ similar to Yii 1.x, however there are still a lot of differences that you'll need to reload. While Yii2 isn't very well documented yet, I wrote this entire blog post just by looking at the source code on Github. The code for Yii2 is pretty well documented, and since the methods are similar to Yii 1.x, it was easy to find what I was looking for, either through examples or just the source code for various classes.
If you'd like the browse the source code for this project, [you can download it from Github.](https://github.com/charlesportwoodii/yii2-blog-demo).
If you'd like to play around with the demo itself without installing it, you can browse the app at [yii2.erianna.com](http://yii2.erianna.com).
The original blog post for this entry can be found on my personal blog here: [Getting Started With Yii Framework 2](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)
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](https://www.erianna.com/getting-starting-with-yii-framework-2-old) 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](http://yf2.erianna.com)
[Original Blog Entry](https://www.erianna.com/getting-starting-with-yii-framework-2)
### 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](https://www.erianna.com/https://www.erianna.com/getting-starting-with-yii-framework-2)
2. My name and Copyright __MUST__ be provided in the reproduction:
Copyright © 2013 Charles R. Portwood II | [Erianna](https://www.erianna.com)
### 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](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).