Yii2 Application Development Cookbook - Third Edition

4.8 (5 reviews total)
By Andrew Bogdanov , Dmitry Eliseev
  • Instant online access to over 8,000+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Fundamentals

About this book

Yii is a free, open source web application development framework written in PHP5 that promotes clean DRY design and encourages rapid development. It works to streamline your application development time and helps to ensure an extremely efficient, extensible, and maintainable end product. Being extremely performance optimized, Yii is a perfect choice for any size project. However, it has been built with sophisticated, enterprise applications in mind. You have full control over the configuration from head-to-toe (presentation-to-persistence) to conform to your enterprise development guidelines. It comes packaged with tools to help test and debug your application, and has clear and comprehensive documentation.

This book is a collection of Yii2 recipes. Each recipe is represented as a full and independent item, which showcases solutions from real web-applications. So you can easily reproduce them in your environment and learn Yii2 fast and without tears. All recipes are explained with step-by-step code examples and clear screenshots.

Yii2 is like a suit that looks great off the rack, but is also very easy to tailor to fit your needs. Virtually every component of the framework is extensible. This book will show how to use official extensions, extend any component, or write a new one.

This book will help you create modern web applications quickly, and make sure they perform well using examples and business logic from real life. You will deal with the Yii command line, migrations, and assets. You will learn about role-based access, security, and deployment. We’ll show you how to easily get started, configure your environment, and be ready to write web applications efficiently and quickly.

Publication date:
November 2016
Publisher
Packt
Pages
584
ISBN
9781785281761

 

Chapter 1. Fundamentals

In this chapter, we will cover the following topics:

  • Installing the framework

  • Application templates

  • Dependency injection container

  • Service locator

  • Code generation

  • Configuring components

  • Working with events

  • Using external code

 

Introduction


In this chapter we will cover how to install Yii Framework and about possible techniques of installation. We will introduce you to application templates: basic and advanced and their difference between them. Then you will learn about dependency injection container. This chapter contains info about model events, which trigger after some actions such as model saving, updating and others. We will learn how to use external code which will include ZendFramework, Laravel, or Symfony. We will also be learning about how to update your yii-1.x.x based application to yii2 step-by-step.

 

Installing the framework


Yii2 is a modern PHP framework provided as a Composer package. In this recipe, we will install the framework via the Composer package manager and configure the database connection for our application.

Getting ready

First of all, install the Composer package manager on your system.

Note

Note: If you use the OpenServer application on Windows, than the composer command already exists in the OpenServer terminal.

In Mac or Linux download the installer from https://getcomposer.org/download/ and install it globally by using the following command:

sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

In Windows without OpenServer download and run Composer-Setup.exe from the https://getcomposer.org/doc/00-intro.md page.

If you do not have administrative privileges on the system then as an alternative you can just download the https://getcomposer.org/composer.phar raw file and use the php composer.phar call instead of single the composer command.

After installation run in your terminal:

composer

Or (if you just download archive) its alternative:

php composer.phar

When the installation succeeds you will see the following response:

   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ '__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.2.0 2016-07-18 11:27:19

Right now you can install any package from the https://packagist.org repository.

How to do it…

You can install basic or advanced application templates. In order to learn about the differences between the templates see the Application templates recipe.

Note

Note that during installation the Composer package manager gets a lot of information from the GitHub site. GitHub may limit requests for anonymous users. In this case Composer asks you to input your access token. You should just register the https://github.com site and generate a new token via the https://github.com/blog/1509-personal-api-tokens guide.

Installing a basic project template

Carry out the following steps for installing basic project template:

  1. As the first step open your terminal and install Bower-to-Composer adapter:

    composer global require "fxp/composer-asset-plugin:^1.2.0"
    

    It provides a simple way to load related non-PHP packages (JavaScript and CSS) from the Bower repository.

  2. Create a new application in the new basic directory:

    composer create-project --prefer-dist yiisoft/yii2-app-basic basic
    
  3. Check that your PHP contains the required extensions:

    cd basic
    php requirements.php
    

    Note

    Note: PHP in command-mode and in web-interface mode can use different php.ini files with different configurations and different extensions.

  4. Create a new database (if it is needle for your project) and configure it in the config/db.php file.

  5. Try to run application via the following console command:

    php yii serve
    
  6. Check in your browser that the application works by the http://localhost:8080 address:

For permanent working create a new host in your server (Apache, Nginx, and so on) and set the web directory as a document root of the host.

Installing advanced project template

Carry out the following steps for installing advanced project template:

  1. As the first step open your terminal install Bower-to-Composer adapter:

    composer global require "fxp/composer-asset-plugin:^1.2.0"
    

    It provides a simple way to load related non-PHP packages (JavaScript and CSS) from the Bower repository.

  2. Create a new application in the new basic directory:

    composer create-project --prefer-dist yiisoft/yii2-app-advanced advanced
    
  3. The new application does not contains local configuration files and index.php entry scripts yet. To generate the files just init a working environment:

    cd advanced
    php init
    

    During initialization select the Development environment.

  4. Check that your PHP contains the required extensions:

    php requirements.php
    

    Note

    Note: PHP in command-line mode and in web-interface mode can use different php.ini files with different configuration and different extensions.

  5. Create a new database and configure it in the generated common/config/main-local.php file.

  6. Apply the application migrations:

    php yii migrate
    

    This command will automatically create a user table in your database.

  7. Try to run a frontend application by the following console command:

    php yii serve [email protected]/web --port=8080
    

    Then run the backend in an other terminal window:

    php yii serve [email protected]/web --port=8090
    
  8. Check in your browser that the application works via the http://localhost:8080 and http://localhost:8090 addresses:

Create two new hosts for backend and frontend application in your server (Apache, Nginx, and so on) and set the backend/web and frontend/web directories as document roots of the hosts.

How it works…

First of all, we installed the Composer package manager and the Bower asset plugin.

After we installed the application via the composer create-project command, the command creates a new empty directory, clones the source code of application template and loads all its inner dependencies (framework and other components) into the vendor subdirectory.

If needed, we will initialize application configuration and set up a new database.

We can check system requirements via running the requirements.php script in console or browser mode.

And after cloning of the code we can configure our own PHP server to work with the web directories as the server's document roots.

See also

 

Application templates


Yii2 has two application templates for development: basic and advanced. What is the difference between basic and advanced templates?

The names are confusing. Some people in the end choose basic because advanced may sound repulsive. In this chapter we will look at the differences.

How to do it…

Please refer to the Installing the framework recipe's How to do it… section to understand and learn how to install different templates.

How it works…

The advanced template has a custom system of configurations. It is developed so that a team can work together on a project but each developer can customize their own configurations for development, testing, and other environments.

Configuration environments can be complicated and normally aren't used when you develop alone.

The advanced template has frontend and backend folders for the frontend and backend parts of the web application accordingly. So you can configure a separate host for each folder and thereby isolate the frontend and backend part.

This is a simple way to organize files into directories and configure the web server. You can easily do the same thing in the basic template.

Neither front/back-end separation nor user management is on its own a good reason to choose the advanced template. It's better to adapt these features to your app—you'll learn more and won't get the difficult config problem.

If you will be working on the project with a team and you might need configuration flexibility, use different environments to develop and in this case a better choice would be the advanced application template. If you will be working alone and your project is simple you should choose the basic application template.

 

Dependency injection container


Dependency Inversion Principle (DIP) suggests we create modular low-coupling code with the help of extracting clear abstraction subsystems.

For example, if you want to simplify a big class you can split it into many chunks of routine code and extract every chunk into a new simple separated class.

The principle says that your low-level chunks should implement an all-sufficient and clear abstraction, and high-level code should work only with this abstraction and not low-level implementation.

When we split a big multitask class into small specialized classes, we face the issue of creating dependent objects and injecting them into each other.

If we could create one instance before:

$service = new MyGiantSuperService();

And after splitting we will create or get all dependent items and build our service:

$service = new MyService(
    new Repository(new PDO('dsn', 'username', 'password')),
    new Session(),
    new Mailer(new SmtpMailerTransport('username', 'password', host')),
    new Cache(new FileSystem('/tmp/cache')),
);

Dependency injection container is a factory that allows us to not care about building our objects. In Yii2 we can configure a container only once and use it for retrieving our service like this:

$service = Yii::$container->get('app\services\MyService')

We can also use this:

$service = Yii::createObject('app\services\MyService')

Or we ask the container to inject it as a dependency in the constructor of an other service:

use app\services\MyService;
class OtherService
{
    public function __construct(MyService $myService) { … }
}

When we will get the OtherService instance:

$otherService = Yii::createObject('app\services\OtherService')

In all cases the container will resolve all dependencies and inject dependent objects in each other.

In the recipe we create shopping cart with storage subsystem and inject the cart automatically into controller.

Getting ready

Create a new application by using the Composer package manager, as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-startinstallation.html.

How to do it…

Carry out the following steps:

  1. Create a shopping cart class:

    <?php
    namespace app\cart;
    
    use app\cart\storage\StorageInterface;
    
    class ShoppingCart
    {
        private $storage;
    
        private $_items = [];
    
        public function __construct(StorageInterface $storage)
        {
            $this->storage = $storage;
        }
    
        public function add($id, $amount)
        {
            $this->loadItems();
            if (array_key_exists($id, $this->_items)) {
                $this->_items[$id]['amount'] += $amount;
            } else {
                $this->_items[$id] = [
                    'id' => $id,
                    'amount' => $amount,
                ];
            }
            $this->saveItems();
        }
    
        public function remove($id)
        {
            $this->loadItems();
            $this->_items = array_diff_key($this->_items, [$id => []]);
            $this->saveItems();
        }
    
        public function clear()
        {
            $this->_items = [];
            $this->saveItems();
        }
    
        public function getItems()
        {
            $this->loadItems();
            return $this->_items;
        }
    
        private function loadItems()
        {
            $this->_items = $this->storage->load();
        }
    
        private function saveItems()
        {
            $this->storage->save($this->_items);
        }
    }
  2. It will work only with own items. Instead of built-in storing items to session it will delegate this responsibility to any external storage class, which will implement the StorageInterface interface.

  3. The cart class just gets the storage object in its own constructor, saves it instance into private $storage field and calls its load() and save() methods.

  4. Define a common cart storage interface with the required methods:

    <?php
    namespace app\cart\storage;
    
    interface StorageInterface
    {
        /**
        * @return array of cart items
        */
        public function load();
    
        /**
        * @param array $items from cart
        */
        public function save(array $items);
    }
  5. Create a simple storage implementation. It will store selected items in a server session:

    <?php
    namespace app\cart\storage;
    
    use yii\web\Session;
    
    class SessionStorage implements StorageInterface
    {
        private $session;
        private $key;
    
        public function __construct(Session $session, $key)
        {
            $this->key = $key;
            $this->session = $session;
        }
    
        public function load()
        {
            return $this->session->get($this->key, []);
        }
    
        public function save(array $items)
        {
            $this->session->set($this->key, $items);
        }
    }
  6. The storage gets any framework session instance in the constructor and uses it later for retrieving and storing items.

  7. Configure the ShoppingCart class and its dependencies in the config/web.php file:

    <?php
    use app\cart\storage\SessionStorage;
    
    Yii::$container->setSingleton('app\cart\ShoppingCart');
    
    Yii::$container->set('app\cart\storage\StorageInterface', function() {
        return new SessionStorage(Yii::$app->session, 'primary-cart');
    });
    
    $params = require(__DIR__ . '/params.php');
    
    //…
  8. Create the cart controller with an extended constructor:

    <?php
    namespace app\controllers;
    
    use app\cart\ShoppingCart;
    use app\models\CartAddForm;
    use Yii;
    use yii\data\ArrayDataProvider;
    use yii\filters\VerbFilter;
    use yii\web\Controller;
    
    class CartController extends Controller
    {
        private $cart;
    
        public function __construct($id, $module, ShoppingCart $cart, $config = [])
        {
            $this->cart = $cart;
            parent::__construct($id, $module, $config);
        }
    
        public function behaviors()
        {
            return [
                'verbs' => [
                    'class' => VerbFilter::className(),
                    'actions' => [
                        'delete' => ['post'],
                    ],
                ],
            ];
        }
    
        public function actionIndex()
        {
            $dataProvider = new ArrayDataProvider([
                'allModels' => $this->cart->getItems(),
            ]);
    
            return $this->render('index', [
                'dataProvider' => $dataProvider,
            ]);
        }
    
        public function actionAdd()
        {
            $form = new CartAddForm();
    
            if ($form->load(Yii::$app->request->post()) && $form->validate()) {
                $this->cart->add($form->productId, $form->amount);
                return $this->redirect(['index']);
            }
    
            return $this->render('add', [
                'model' => $form,
            ]);
        }
    
        public function actionDelete($id)
        {
            $this->cart->remove($id);
    
            return $this->redirect(['index']);
        }
    }
  9. Create a form:

    <?php
    namespace app\models;
    
    use yii\base\Model;
    
    class CartAddForm extends Model
    {
        public $productId;
        public $amount;
    
        public function rules()
        {
            return [
                [['productId', 'amount'], 'required'],
                [['amount'], 'integer', 'min' => 1],
            ];
        }
    }
  10. Create the views/cart/index.php view:

    <?php
    use yii\grid\ActionColumn;
    use yii\grid\GridView;
    use yii\grid\SerialColumn;
    use yii\helpers\Html;
    
    /* @var $this yii\web\View */
    /* @var $dataProvider yii\data\ArrayDataProvider */
    
    $this->title = 'Cart';
    $this->params['breadcrumbs'][] = $this->title;
    ?>
    <div class="cart-index">
        <h1><?= Html::encode($this->title) ?></h1>
    
        <p><?= Html::a('Add Item', ['add'], ['class' => 'btn btn-success']) ?></p>
    
        <?= GridView::widget([
            'dataProvider' => $dataProvider,
            'columns' => [
                ['class' => SerialColumn::className()],
    
                'id:text:Product ID',
                'amount:text:Amount',
    
                [
                    'class' => ActionColumn::className(),
                    'template' => '{delete}',
                ]
            ],
        ]) ?>
    </div>
  11. Create the views/cart/add.php view:

    <?php
    use yii\helpers\Html;
    use yii\bootstrap\ActiveForm;
    
    /* @var $this yii\web\View */
    /* @var $form yii\bootstrap\ActiveForm */
    /* @var $model app\models\CartAddForm */
    
    $this->title = 'Add item';
    $this->params['breadcrumbs'][] = ['label' => 'Cart', 'url' => ['index']];
    $this->params['breadcrumbs'][] = $this->title;
    ?>
    <div class="cart-add">
        <h1><?= Html::encode($this->title) ?></h1>
    
        <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
            <?= $form->field($model, 'productId') ?>
            <?= $form->field($model, 'amount') ?>
            <div class="form-group">
                <?= Html::submitButton('Add', ['class' => 'btn btn-primary']) ?>
            </div>
        <?php ActiveForm::end(); ?>
    </div>
  12. Add link items into the main menu:

    ['label' => 'Home', 'url' => ['/site/index']],
    ['label' => 'Cart', 'url' => ['/cart/index']],
    ['label' => 'About', 'url' => ['/site/about']],
    // …
  13. Open the cart page and try to add rows:

How it works…

In this case we have the main ShoppingCart class with a low-level dependency, defined by an abstraction interface:

class ShoppingCart
{
    public function __construct(StorageInterface $storage) { … }
}

interface StorageInterface
{
   public function load();
   public function save(array $items);
}

And we have some an implementation of the abstraction:

class SessionStorage implements StorageInterface
{
    public function __construct(Session $session, $key) { … }
}

Right now we can create an instance of the cart manually like this:

$storage = new SessionStorage(Yii::$app->session, 'primary-cart');
$cart = new ShoppingCart($storage)

It allows us to create a lot of different implementations such as SessionStorage, CookieStorage, or DbStorage. And we can reuse the framework-independent ShoppingCart class with StorageInterface in different projects and different frameworks. We must only implement the storage class with the interface's methods for needed framework.

But instead of manually creating an instance with all dependencies, we can use a dependency injection container.

By default the container parses the constructors of all classes and recursively creates all the required instances. For example, if we have four classes:

class A {
     public function __construct(B $b, C $c) { … }
}

class B {
    ...
}

class C {
    public function __construct(D $d) { … }
}

class D {
    ...
}

We can retrieve the instance of class A in two ways:

$a = Yii::$container->get('app\services\A')
// or
$a = Yii::createObject('app\services\A')

And the container automatically creates instances of the B, D, C, and A classes and injects them into each other.

In our case we mark the cart instance as a singleton:

Yii::$container->setSingleton('app\cart\ShoppingCart');

This means that the container will return a single instance for every repeated call instead of creating the cart again and again.

Besides, our ShoppingCart has the StorageInterface type in its own constructor and the container does know what class it must instantiate for this type. We must manually bind the class to the interface like this:

Yii::$container->set('app\cart\storage\StorageInterface', 'app\cart\storage\CustomStorage',);

But our SessionStorage class has non-standard constructor:

class SessionStorage implements StorageInterface
{
    public function __construct(Session $session, $key) { … }
}

Therefore we use an anonymous function to manually creatie the instance:

Yii::$container->set('app\cart\storage\StorageInterface', function() {
    return new SessionStorage(Yii::$app->session, 'primary-cart');
});

And after all we can retrieve the cart object from the container manually in our own controllers, widgets, and other places:

$cart = Yii::createObject('app\cart\ShoppingCart')

But every controller and other object will be created via the createObject method inside the framework. And we can use injection of cart via the controller constructor:

class CartController extends Controller
{
    private $cart;

    public function __construct($id, $module, ShoppingCart $cart, $config = [])
    {
        $this->cart = $cart;
        parent::__construct($id, $module, $config);
    }

    // ...
}

Use this injected cart object:

public function actionDelete($id)
{
    $this->cart->remove($id);
    return $this->redirect(['index']);
}

See also

 

Service locator


Instead of manually creating instances of different shared services (application components) we can get them from a special global object, which contains configurations and instances of all components.

A service locator is a global object that contains a list of components or definitions, uniquely identified by an ID, and allow us to retrieve any needed instance by its ID. The locator creates a single instance of the component on-the-fly at the first call and returns a previous instance at the subsequent calls.

In this recipe, we will create a shopping cart component and will write a cart controller for working with it.

Getting ready

Create a new application by using the Composer package manager, as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-start-installation.html.

How to do it…

Carry out the following steps to create a shopping cart component:

  1. Create a shopping cart component. It will store selected items in a user session:

    <?php
    namespace app\components;
    
    use Yii;
    use yii\base\Component;
    
    class ShoppingCart extends Component
    {
        public $sessionKey = 'cart';
    
        private $_items = [];
    
        public function add($id, $amount)
        {
            $this->loadItems();
            if (array_key_exists($id, $this->_items)) {
                $this->_items[$id]['amount'] += $amount;
            } else {
                $this->_items[$id] = [
                    'id' => $id,
                    'amount' => $amount,
                ];
            }
           $this->saveItems();
        }
    
        public function remove($id)
        {
            $this->loadItems();
            $this->_items = array_diff_key($this->_items, [$id => []]);
            $this->saveItems();
        }
    
        public function clear()
        {
            $this->_items = [];
            $this->saveItems();
        }
    
        public function getItems()
        {
            $this->loadItems();
            return $this->_items;
        }
    
        private function loadItems()
        {
            $this->_items = Yii::$app->session->get($this->sessionKey, []);
        }
    
        private function saveItems()
        {
            Yii::$app->session->set($this->sessionKey, $this->_items);
        }
    }
  2. Register the ShoppingCart in service locator as an application component in the config/web.php file:

    'components' => [
        …
        'cart => [
            'class' => 'app\components\ShoppingCart',
            'sessionKey' => 'primary-cart',
        ],
    ]
  3. Create a cart controller:

    <?php
    namespace app\controllers;
    
    use app\models\CartAddForm;
    use Yii;
    use yii\data\ArrayDataProvider;
    use yii\filters\VerbFilter;
    use yii\web\Controller;
    
    class CartController extends Controller
    {
        public function behaviors()
        {
            return [
                'verbs' => [
                    'class' => VerbFilter::className(),
                    'actions' => [
                        'delete' => ['post'],
                    ],
                ],
            ];
        }
    
        public function actionIndex()
        {
            $dataProvider = new ArrayDataProvider([
                'allModels' => Yii::$app->cart->getItems(),
            ]);
    
            return $this->render('index', [
                'dataProvider' => $dataProvider,
            ]);
        }
    
        public function actionAdd()
        {
            $form = new CartAddForm();
    
            if ($form->load(Yii::$app->request->post()) && $form->validate()) {
                Yii::$app->cart->add($form->productId, $form->amount);
                return $this->redirect(['index']);
            }
    
            return $this->render('add', [
                'model' => $form,
            ]);
        }
    
        public function actionDelete($id)
        {
            Yii::$app->cart->remove($id);
    
            return $this->redirect(['index']);
        }
    }
  4. Create a form:

    <?php
    namespace app\models;
    
    use yii\base\Model;
    
    class CartAddForm extends Model
    {
        public $productId;
        public $amount;
    
        public function rules()
        {
            return [
                [['productId', 'amount'], 'required'],
                [['amount'], 'integer', 'min' => 1],
            ];
        }
    }
  5. Create the views/cart/index.php view:

    <?php
    use yii\grid\ActionColumn;
    use yii\grid\GridView;
    use yii\grid\SerialColumn;
    use yii\helpers\Html;
    
    /* @var $this yii\web\View */
    /* @var $dataProvider yii\data\ArrayDataProvider */
    
    $this->title = 'Cart';
    $this->params['breadcrumbs'][] = $this->title;
    ?>
    <div class="site-contact">
        <h1><?= Html::encode($this->title) ?></h1>
    
        <p><?= Html::a('Add Item', ['add'], ['class' => 'btn btn-success']) ?></p>
    
        <?= GridView::widget([
            'dataProvider' => $dataProvider,
            'columns' => [
                ['class' => SerialColumn::className()],
    
                'id:text:Product ID',
                'amount:text:Amount',
    
                [
                   'class' => ActionColumn::className(),
                   'template' => '{delete}',
                ]
            ],
        ]) ?>
    </div>
  6. Create the views/cart/add.php view:

    <?php
    use yii\helpers\Html;
    use yii\bootstrap\ActiveForm;
    
    /* @var $this yii\web\View */
    /* @var $form yii\bootstrap\ActiveForm */
    /* @var $model app\models\CartAddForm */
    
    $this->title = 'Add item';
    $this->params['breadcrumbs'][] = ['label' => 'Cart', 'url' => ['index']];
    $this->params['breadcrumbs'][] = $this->title;
    ?>
    <div class="site-contact">
        <h1><?= Html::encode($this->title) ?></h1>
    
        <?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
            <?= $form->field($model, 'productId') ?>
            <?= $form->field($model, 'amount') ?>
            <div class="form-group">
                <?= Html::submitButton('Add', ['class' => 'btn btn-primary']) ?>
            </div>
        <?php ActiveForm::end(); ?>
    </div>
  7. Add a link item into the main menu:

    ['label' => 'Home', 'url' => ['/site/index']],
    ['label' => 'Cart', 'url' => ['/cart/index']],
    ['label' => 'About', 'url' => ['/site/about']],
    // …
  8. Open the cart page and try to add rows:

How it works…

First of all we created our own class with a public sessionKey option:

<?php
namespace app\components;
use yii\base\Component;

class ShoppingCart extends Component
{
    public $sessionKey = 'cart';

    // …
}

Secondly, we added the component definition into the components section of the configuration file:

'components' => [
    …
    'cart => [
        'class' => 'app\components\ShoppingCart',
        'sessionKey' => 'primary-cart',
    ],
]

Right now we can retrieve the component instance in two ways:

$cart = Yii::$app->cart;
$cart = Yii::$app->get('cart');

And we can use this object in our own controllers, widgets, and other places.

When we call any component such as cart:

Yii::$app->cart

We call the virtual property of the Application class instance in the Yii::$app static variable. But the yii\base\Application class extends the yii\base\Module class, which extends the yii\di\ServiceLocator class with the __get magic method. This magic method just calls the get() method of the yii\di\ServiceLocator class:

namespace yii\di;

class ServiceLocator extends Component
{
    private $_components = [];
    private $_definitions = [];

    public function __get($name)
    {
        if ($this->has($name)) {
            return $this->get($name);
        } else {
            return parent::__get($name);
        }
    }
    // …
}

As a result it is an alternative to directly calling the service via the get method:

Yii::$app->get('cart);

When we get a component from the get method of service locator, the locator finds needed definition in its _definitions list and if successful it creates a new object by the definition on the fly, registers it in its own list of complete instances _components and returns the object.

If we get some component, multiplying the locator will always return the previous saved instance again and again:

$cart1 = Yii::$app->cart;
$cart2 = Yii::$app->cart;
var_dump($cart1 === $cart2); // bool(true)

It allows us to use the shared single cart instance Yii::$app->cart or single database connection Yii::$app->db instead of creating one large set from scratch again and again.

See also

 

Code generation


Yii2 provides the powerful module Gii to generate models, controllers, and views, which you can easily modify and customize. It's a really helpful tool for fast and quick development.

In this section we will explore how to use Gii and generate code. For example you have a database with one table named film and you would like to create an application with CRUD operations for this table. It's easy.

Getting ready

  1. Create a new application by using composer as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-start-installation.html.

  2. Download the Sakila database from http://dev.mysql.com/doc/index-other.html.

  3. Execute the downloaded SQLs: first the schema then the data.

  4. Configure the database connection in config/main.php to use the Sakila database.

  5. Run your web-server by ./yii serve.

How to do it…

  1. Go to http://localhost:8080/index.php?r=gii and select Model Generator.

  2. Fill out Table Name as actor and Model Class as Actor and press button Generate at the bottom of page.

  3. Return tothe main Gii menu by clicking the yii code generator logo on the header and choose CRUD Generator.

  4. Fill out the Model Class field as app\models\Actor and Controller Class as app\controllers\ActorController.

  5. Press the Preview button at the bottom of page and then press green button Generate.

  6. Check the result via http://localhost:8080/index.php?actor/create.

How it works…

If you check your project structure you will see autogenerated code:

Firstly we've created an Actor model. Gii automatically creates all model rules which depends on mysql field types. For example, if in your MySQL actor table's fields first_name and last_name have IS NOT NULL flag then Yii automatically creates rule for it required and sets max length 45 symbols because in our database max length of this field is set up as 45.

public function rules()
{
    return [
        [['first_name', 'last_name'], 'required'],
        [['last_update'], 'safe'],
        [['first_name', 'last_name'], 'string', 'max' => 45],
    ];
}

Also Yii creates relationship between models automatically, based on foreign keys you added to your database. In our case two relations were created automatically.

public function getFilmActors()
{
    return $this->hasMany(FilmActor::className(), ['actor_id' => 'actor_id']);
}

public function getFilms()
{
    return $this->hasMany(Film::className(), ['film_id' => 'film_id'])->viaTable('film_actor', ['actor_id' => 'actor_id']);
}

This relationship has been created because we have two foreign keys in our database. The film_actor table has foreign key fk_film_actor_actor which points to actor table fields actor_id and fk_film_actor_film which points to film table field film_id.

Notice that you haven't generated FilmActor model yet. So if you would develop full-app versus demo you had to generate Film, FilmActor models also. For the rest of the pieces, refer to http://www.yiiframework.com/doc-2.0/guide-start-gii.html.

 

Configuring components


Yii is a very customizable framework. Moreover, as in all customizable code, there should be a convenient way to set up different application parts. In Yii, this is provided through configuration files located at config.

Getting ready

Create a new application by using the Composer package manager as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-startinstallation.html.

How to do it…

If you have worked with Yii before, then you have probably configured a database connection:

return [
    …
    'components' => [
        'db' => [
            'class' => 'system.db.CDbConnection',
            'dsn' => 'mysql:host=localhost;dbname=database_name',
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
        ],
        …
    ],
    …
];

This way of configuring components is used when you want to use a component across all application parts. With the preceding configuration, you can access a component by its name, such as Yii::$app->db.

How it works…

When you are using the Yii::$app->db component for the first time directly or through an Active Record model, Yii creates a component and initializes its public properties with the corresponding values provided in db array under the components section of the application configuration file. In the preceding code, dsn value will be assigned to yii\db\Connection::dsn, username will be assigned to Connection::username, and so on.

If you want to find out what charset stands for or want to know what else you can configure in the db component, then you need to know its class. In the case of the db component, the class is yii\db\Connection. You can just open the class and look for its public properties, which you can set from config.

In the preceding code, the class property is a bit special because it is used to specify the component class name. It does not exist in the yii\db\Connection class. Therefore, it can be used to override a class as follows:

return [
    …
    'components' => [
        'db' => [
            'class' => app\components\MyConnection',
            …
        ],
        …
    ],
     …
);

This way, you can override each application component; this is very useful whenever a standard component does not fit your application.

Built-in components

Now, let's find out which standard Yii application components you can configure. There are two application types bundled with Yii:

  • Web application (yii\webApplication)

  • Console application (yii\console\Application)

Both are extended from yii\base\Application, so both console and web applications share its components.

You can get the component names from the source code of the coreComponents() application's method.

You can add your own application components (classes extended from yii\base\Component) by simply adding new configuration items and pointing their class properties to your custom classes.

See also

 

Working with events


Yii's events provide a simple implementation, which allows you to listen and subscribe to various events that occur in your web-application. For example, you may wish to send a notification about a new article to followers each time you publish new material.

Getting ready

  1. Create a new application by using the Composer package manager, as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-start-installation.html.

  2. Execute the following SQL code on your server to create the article table:

    CREATE TABLE 'article' (
        'id' int(11) NOT NULL AUTO_INCREMENT,
        'name' varchar(255) DEFAULT NULL,
        'description' text,
        PRIMARY KEY ('id')
    ) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
  3. Generate the Article model using Gii.

  4. Run your webserver by ./yii serve command.

How to do it…

  1. Add an action test to \controllers\SiteController:

    public function actionTest()
    {
        $article = new Article();
        $article->name = 'Valentine\'s Day\'s coming? Aw crap! I forgot to get a girlfriend again!';
        $article->description = 'Bender is angry at Fry for dating a robot. Stay away from our women.
        You've got metal fever, boy. Metal fever';
    
        // $event is an object of yii\base\Event or a child class
        $article->on(ActiveRecord::EVENT_AFTER_INSERT, function($event) {
            $followers = ['[email protected]', '[email protected]', '[email protected]' ];
            foreach($followers as $follower) {
                Yii::$app->mailer->compose()
                    ->setFrom('[email protected]')
                    ->setTo($follower)
                    ->setSubject($event->sender->name)
                    ->setTextBody($event->sender->description)
                    ->send();
            }
            echo 'Emails has been sent';
        });
    
        if (!$article->save()) {
            echo VarDumper::dumpAsString($article->getErrors());
        };
    }
  2. Update the config/web.php component mailer using the following code.

    'mailer' => [
        'class' => 'yii\swiftmailer\Mailer',
        'useFileTransport' => false,
    ],
  3. Run this URL in your browser: http://localhost:8080/index.php?r=site/test.

  4. Also check http://www.fakemailgenerator.com/inbox/teleworm.u s/john2/.

How it works…

We've created an Article model and added a handler for the ActiveRecord::EVENT_AFTER_INSERT event to our Article model. It means that every time we save a new article an event is triggered and our attached handler will be called.

In the real-world, we would like to notify our blog followers each time we publish a new article. In a real application we would have a follower or user table and with different blog sections not only single blog. In this example, after saving our model we notify our followers [email protected], [email protected], and [email protected]. In the last step we just prove that users have received our notifications, particularly john2. You can create your own event with any name. In this example we use a built-in event called ActiveRecord::EVENT_AFTER_INSERT, which is called after each insert to the database.

For example, we can create our own event. Just add a new actionTestNew with the following code:

public function actionTestNew()
{
    $article = new Article();
    $article->name = 'Valentine\'s Day\'s coming? Aw crap! I forgot to get a girlfriend again!';
    $article->description = 'Bender is angry at Fry for dating a robot. Stay away from our women.
    You've got metal fever, boy. Metal fever';

    // $event is an object of yii\base\Event or a child class
    $article->on(Article::EVENT_OUR_CUSTOM_EVENT, function($event) {
        $followers = ['[email protected]', '[email protected]', '[email protected]' ];
        foreach($followers as $follower) {
            Yii::$app->mailer->compose()
                ->setFrom('[email protected]')
                ->setTo($follower)
                ->setSubject($event->sender->name)
                ->setTextBody($event->sender->description)
                ->send();
        }
        echo 'Emails have been sent';
    });

    if ($article->save()) {
        $article->trigger(Article::EVENT_OUR_CUSTOM_EVENT);
    }
}

Also add the EVENT_OUR_CUSTOM_EVENT constant to models/Article as:

class Article extends \yii\db\ActiveRecord
{
    CONST EVENT_OUR_CUSTOM_EVENT = 'eventOurCustomEvent';
…
}

Run http://localhost:8080/index.php?r=site/test-new.

You should see the same result and all notifications to followers will be sent again. The main difference is we used our custom event name.

After the save, we've triggered our event. Events may be triggered by calling the yii\base\Component::trigger() method. The method requires an event name, and optionally an event object that describes the parameters to be passed to the event handlers.

See also

For more information about events refer to http://www.yiiframework.com/doc-2.0/guide-concept-events.html

 

Using external code


Package repositories, PSR standards, and social coding provide us with lots of high-quality reusable libraries and other components with free licenses. We can just install any external component in project instead of reengineering them from scratch. It improves development performance and makes for higher-quality code.

Getting ready

Create a new application by using the Composer package manager as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-start-installation.html.

How to do it…


In this recipe we will try to attach some libraries manually and via Composer.

Installing a library via Composer

When you use NoSQL or other databases without autoincrement primary keys, you must generate unique identifiers manually. For example, you can use Universally Unique Identifier (UUID) instead of a numerical one. Let's do it:

  1. Install https://github.com/ramsey/uuid component via Composer:

    composer require ramsey/uuid
    
  2. Create a demonstration console controller:

    <?php
    namespace app\commands;
    
    use Ramsey\Uuid\Uuid;
    use yii\console\Controller;
    
    class UuidController extends Controller
    {
        public function actionGenerate()
        {
            $this->stdout(Uuid::uuid4()->toString() . PHP_EOL);
            $this->stdout(Uuid::uuid4()->toString() . PHP_EOL);
            $this->stdout(Uuid::uuid4()->toString() . PHP_EOL);
            $this->stdout(Uuid::uuid4()->toString() . PHP_EOL);
            $this->stdout(Uuid::uuid4()->toString() . PHP_EOL);
        }
    }
  3. And just run it:

    ./yii uuid/generate
  4. If successful, you'll see the following output:

    25841e6c-6060-4a81-8368-4d99aa3617dd
    fcac910a-a9dc-4760-8528-491c17591a26
    4d745da3-0a6c-47df-aee7-993a42ed915c
    0f3e6da5-88f1-4385-9334-b47d1801ca0f
    21a28940-c749-430d-908e-1893c52f1fe0
  5. That's it! Now you can use the Ramsey\Uuid\Uuid class in your project.

Installing libraries manually

We can install a library automatically when it is provided as a Composer package. In other cases we must install it manually.

For example, create some library examples:

  1. Create the awesome/namespaced/Library.php file with the following code:

    <?php
    namespace awesome\namespaced;
    
    class Library
    {
        public function method()
        {
            return 'I am an awesome library with namespace.';
        }
    }
  2. Create the old/OldLibrary.php file:

    <?php
    class OldLibrary
    {
        function method()
        {
            return 'I am an old library without namespace.';
        }
    }
  3. Create a set of functions as an old/functions.php file:

    <?php
    function simpleFunction()
    {
        return 'I am a simple function.';
    }

    And now set up this file in our application:

  4. Define the new alias for the awesome library namespace root in the config/web.php file (in aliases section):

    $config = [
        'id' => 'basic',
        'basePath' => dirname(__DIR__),
        'bootstrap' => ['log'],
        'aliases' => [
            '@awesome' => '@app/awesome',
        ],
        'components' => [
            // …
        ],
        'params' => // …
    ];

    or via the setAlias method:

    Yii::setAlias('@awesome', '@app/awesome');
  5. Define a simple class file path at the top of the config/web.php file:

    Yii::$classMap['OldLibrary'] = '@old/OldLibrary.php';
  6. Configure autoloading of the functions.php file in composer.json:

    "require-dev": {
        ...
    },
    "autoload": {
        "files": ["old/functions.php"]
    },
    "config": {
        ...
    },

    And apply the changes:

    composer update
    
  7. And now create an example controller:

    <?php
    namespace app\controllers;
    
    use yii\base\Controller;
    
    class LibraryController extends Controller
    {
        public function actionIndex()
        {
            $awesome = new \awesome\namespaced\Library();
            echo '<pre>' . $awesome->method() . '</pre>';
    
            $old = new \OldLibrary();
            echo '<pre>' . $old->method() . '</pre>';
    
            echo '<pre>' . simpleFunction() . '</pre>';
        }
    }

    And open the page:

Using Yii2 code in other frameworks

If you want to use Yii2 framework code with other frameworks just add Yii2-specific parameters in composer.json:

{
    ...
    "extra": {
        "asset-installer-paths": {
            "npm-asset-library": "vendor/npm",
            "bower-asset-library": "vendor/bower"
        }
    }
}

And install the framework:

composer require yiisoft/yii2

Now open the entry script of your application (on ZendFramework, Laravel, Symfony, and many more), require the Yii2 autoloader, and create the Yii application instance:

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
$config = require(__DIR__ . '/../config/yii/web.php');
new yii\web\Application($config);

That's it! Now you can use Yii::$app instances, models, widgets and other components from Yii2.

How it works…

In the first case we just install a new Composer package in our project and use it, because its composer.json file defines all aspects of autoloading library files.

But in the second case we did not have Composer packages and registered the files in the autoloading mechanism manually. In Yii2 we can use aliases and Yii::$classMap for registering the roots of PSR-4 namespaces and for single files.

But as an alternative we can use Composer autoloader for all cases. Just define an extended autoload section in the composer.json file like this:

"autoload": {
    "psr-0": { "": "old/" },
    "psr-4": {"awesome\\": "awesome/"},
    "files": ["old/functions.php"]
}

Apply the changes using this command:

composer update

Right now you can remove aliases and $classMap definitions from your configuration files and ensure the example page still works correctly:

This example completely uses Composer's autoloader instead of the framework's autoloader.

See also

About the Authors

  • Andrew Bogdanov

    Andrew Bogdanov is a seasoned web developer from Yekaterinburg, Russia with more than six years' experience in industrial development. Since 2010 he has been interested in the Yii and MVC frameworks. He has taken part in projects written in Yii such as a work aggregator for a UK company, high-load projects, real-estate projects, and the development of private projects for the government.

    He has worked on various CMS and frameworks using PHP and MySQL, including Yii, Kohana, Symphony, Joomla, WordPress, CakePHP, and so on. Also, having good hands in integrating third-party APIs such as Payment gateways (PayPal, Facebook, Twitter, and LinkedIn). He is very good in slicing and frontend, so he can provide full information about the Yii framework.
    He is also well-versed in PHP/MYSQL, Yii 1.x.x, Yii 2.x.x, Ajax, Jquery, MVC frameworks, Python, LAMP, HTML/CSS, Mercurial, Git, AngularJS, and adaptive markup. You can visit his blog at http://jehkinen.com.

    In his free time, he likes to visit and talk with new people and discuss web development problems. He is currently working with professionals: http://2amigos.us.

    Browse publications by this author
  • Dmitry Eliseev

    Dmitry Eliseev has been a web developer since 2008 and specializes in server-side programming on the PHP and PHP frameworks. Since 2012 he has authored his personal blog, http://elisdn.ru, about web development in general and about the Yii Framework in particular. His blog has become a well-known resource in the Russian Yii community. He is an active member of a Russian-language forum: http://yiiframework.ru.

    Dmitry is interested in developmental best practices, software architectures, object-oriented programming, and other approaches.

    He is an author and a presenter of practical courses about the principles and best practices of object-oriented programming and the use of version control systems. And also he is an author of webinars, the Yii2 Framework, and common developmental subjects. He practices teaching and counseling by development on frameworks and using the principles of software design and improvements in common code quality. This is his first book.

    Browse publications by this author

Latest Reviews

(5 reviews total)
It's good and nice experience.
愉悦的阅读体验,优质的书。让我爱上了这个网站,以后会经常来的。
Korrekt ár, jó a tartalom.
Book Title
Unlock this full book with a FREE 10-day trial
Start Free Trial