Book Image

Yii2 Application Development Cookbook - Third Edition

By : Sergey Ivanov, Andrew Bogdanov, Dmitry Eliseev
Book Image

Yii2 Application Development Cookbook - Third Edition

By: Sergey Ivanov, Andrew Bogdanov, Dmitry Eliseev

Overview of 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.
Table of Contents (19 chapters)
Yii2 Application Development Cookbook Third Edition
Credits
About the Authors
About the Reviewer
www.PacktPub.com
Preface
Index

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