The Definitive Guide
to
Yii 2.0
Qiang Xue,
Alexander Makarov,
Carsten Brandt,
Klimov Paul
and
the Yii community
Copyright 2014 Yii Software LLC.
Yii2 guide
Contents
1 Introduction 1
1.1 What is Yii . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Upgrading from Version 1.1 . . . . . . . . . . . . . . . . . . . 2
2 Getting Started 15
2.1 Installing Yii . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Running Applications . . . . . . . . . . . . . . . . . . . . . . 20
2.3 Saying Hello . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4 Working with Forms . . . . . . . . . . . . . . . . . . . . . . . 27
2.5 Working with Databases . . . . . . . . . . . . . . . . . . . . . 33
2.6 Generating Code with Gii . . . . . . . . . . . . . . . . . . . . 39
2.7 Looking Ahead . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3 Application Structure 47
3.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.2 Entry Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.3 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.4 Application Components . . . . . . . . . . . . . . . . . . . . . 62
3.5 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.6 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.7 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.8 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
3.9 Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
3.10 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
3.11 Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
3.12 Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
4 Handling Requests 143
4.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
4.2 Bootstrapping . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
4.3 Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
4.4 Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
4.5 Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
iii
iv CONTENTS
4.6 URL Management . . . . . . . . . . . . . . . . . . . . . . . . 158
4.7 Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 165
4.8 Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
5 Key Concepts 171
5.1 Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
5.2 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
5.3 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
5.4 Behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
5.5 Configurations . . . . . . . . . . . . . . . . . . . . . . . . . . 187
5.6 Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
5.7 Class Autoloading . . . . . . . . . . . . . . . . . . . . . . . . 195
5.8 Service Locator . . . . . . . . . . . . . . . . . . . . . . . . . . 197
5.9 Dependency Injection Container . . . . . . . . . . . . . . . . . 199
6 Working with Databases 207
6.1 Database basics . . . . . . . . . . . . . . . . . . . . . . . . . . 207
6.2 Query Builder and Query . . . . . . . . . . . . . . . . . . . . 217
6.3 Active Record . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
6.4 Database Migration . . . . . . . . . . . . . . . . . . . . . . . . 246
7 Getting Data from Users 259
7.1 Working with Forms . . . . . . . . . . . . . . . . . . . . . . . 259
7.2 Validating Input . . . . . . . . . . . . . . . . . . . . . . . . . 263
7.3 Uploading Files . . . . . . . . . . . . . . . . . . . . . . . . . . 276
8 Displaying Data 283
8.1 Data Formatter . . . . . . . . . . . . . . . . . . . . . . . . . . 283
8.2 Data providers . . . . . . . . . . . . . . . . . . . . . . . . . . 289
8.3 Data widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
8.4 Working with Client Scripts . . . . . . . . . . . . . . . . . . . 301
8.5 Theming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
9 Security 307
9.1 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 307
9.2 Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
9.3 Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
10 Caching 329
10.1 Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
10.2 Data Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
10.3 Fragment Caching . . . . . . . . . . . . . . . . . . . . . . . . 337
10.4 Page Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
10.5 HTTP Caching . . . . . . . . . . . . . . . . . . . . . . . . . . 341
CONTENTS v
11 RESTful Web Services 345
11.1 Quick Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
11.2 Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
11.3 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
11.4 Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
11.5 Response Formatting . . . . . . . . . . . . . . . . . . . . . . . 358
11.6 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 361
11.7 Rate Limiting . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
11.8 Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
11.9 Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 368
12 Development Tools 371
12.1 Debug toolbar and debugger . . . . . . . . . . . . . . . . . . . 371
12.2 The Gii code generation tool . . . . . . . . . . . . . . . . . . 375
13 Testing 383
13.1 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
13.2 Testing environment setup . . . . . . . . . . . . . . . . . . . . 385
13.3 Unit Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
13.4 Functional Tests . . . . . . . . . . . . . . . . . . . . . . . . . 386
13.5 Acceptance Tests . . . . . . . . . . . . . . . . . . . . . . . . . 386
13.6 Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
13.7 Managing Fixtures . . . . . . . . . . . . . . . . . . . . . . . . 392
14 Special Topics 395
14.1 Advanced application template . . . . . . . . . . . . . . . . . 395
14.2 Creating your own Application structure . . . . . . . . . . . . 401
14.3 Console applications . . . . . . . . . . . . . . . . . . . . . . . 402
14.4 Core Validators . . . . . . . . . . . . . . . . . . . . . . . . . . 406
14.5 Internationalization . . . . . . . . . . . . . . . . . . . . . . . . 417
14.6 Mailing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
14.7 Performance Tuning . . . . . . . . . . . . . . . . . . . . . . . 432
14.8 Using template engines . . . . . . . . . . . . . . . . . . . . . . 439
14.9 Working with Third-Party Code . . . . . . . . . . . . . . . . . 446
15 Widgets 451
15.1 Bootstrap Widgets . . . . . . . . . . . . . . . . . . . . . . . . 451
15.2 Jquery UI Widgets . . . . . . . . . . . . . . . . . . . . . . . . 453
16 Helpers 455
16.1 Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
vi CONTENTS
Chapter 1
Introduction
1.1 What is Yii
Yii is a high performance, component-based PHP framework for rapidly
developing modern Web applications. The name Yii (pronounced Yee or [ji
:]) means “simple and evolutionary” in Chinese. It can also be thought of
as an acronym for Yes It Is!
1.1.1 What is Yii Best for?
Yii is a generic Web programming framework, meaning that it can be used
for developing all kinds of Web applications using PHP. Because of its
component-based architecture and sophisticated caching support, it is es-
pecially suitable for developing large-scale applications such as portals, for-
ums, content management systems (CMS), e-commerce projects, RESTful
Web services, and so on.
1.1.2 How does Yii Compare with Other Frameworks?
If you’re already familiar with another framework, you may appreciate know-
ing how Yii compares:
• Like most PHP frameworks, Yii implements the MVC (Model-View-
Controller) design pattern and promotes code organization based on
that pattern.
• Yii takes the philosophy that code should be written in a simple yet
elegant way. Yii will never try to over-design things mainly for the
purpose of strictly following some design pattern.
• Yii is a full-stack framework providing many proven and ready-to-
use features: query builders and ActiveRecord for both relational and
1
2 CHAPTER 1. INTRODUCTION
NoSQL databases; RESTful API development support; multi-tier cach-
ing support; and more.
• Yii is extremely extensible. You can customize or replace nearly every
piece of the core’s code. You can also take advantage of Yii’s solid
extension architecture to use or develop redistributable extensions.
• High performance is always a primary goal of Yii.
Yii is not a one-man show, it is backed up by a strong core developer team1,
as well as a large community of professionals constantly contributing to Yii’s
development. The Yii developer team keeps a close eye on the latest Web
development trends and on the best practices and features found in other
frameworks and projects. The most relevant best practices and features
found elsewhere are regularly incorporated into the core framework and ex-
posed via simple and elegant interfaces.
1.1.3 Yii Versions
Yii currently has two major versions available: 1.1 and 2.0. Version 1.1 is
the old generation and is now in maintenance mode. Version 2.0 is a com-
plete rewrite of Yii, adopting the latest technologies and protocols, including
Composer, PSR, namespaces, traits, and so forth. Version 2.0 represents the
current generation of the framework and will receive the main development
efforts over the next few years. This guide is mainly about version 2.0.
1.1.4 Requirements and Prerequisites
Yii 2.0 requires PHP 5.4.0 or above. You can find more detailed requirements
for individual features by running the requirement checker included in every
Yii release.
Using Yii requires basic knowledge of object-oriented programming (OOP),
as Yii is a pure OOP-based framework. Yii 2.0 also makes use of the latest
features of PHP, such as namespaces2 and traits3. Understanding these con-
cepts will help you more easily pick up Yii 2.0.
1.2 Upgrading from Version 1.1
There are many differences between versions 1.1 and 2.0 of Yii as the frame-
work was completely rewritten for 2.0. As a result, upgrading from version
1.1 is not as trivial as upgrading between minor versions. In this guide you’ll
find the major differences between the two versions.
1
http://www.yiiframework.com/about/
2
http://www.php.net/manual/en/language.namespaces.php
3
http://www.php.net/manual/en/language.oop5.traits.php
1.2. UPGRADING FROM VERSION 1.1 3
If you have not used Yii 1.1 before, you can safely skip this section and
turn directly to “Getting started“.
Please note that Yii 2.0 introduces more new features than are covered
in this summary. It is highly recommended that you read through the whole
definitive guide to learn about them all. Chances are that some features you
previously had to develop for yourself are now part of the core code.
1.2.1 Installation
Yii 2.0 fully embraces Composer4, the de facto PHP package manager. In-
stallation of the core framework, as well as extensions, are handled through
Composer. Please refer to the Installing Yii section to learn how to install
Yii 2.0. If you want to create new extensions, or turn your existing 1.1 exten-
sions into 2.0-compatible extensions, please refer to the Creating Extensions
section of the guide.
1.2.2 PHP Requirements
Yii 2.0 requires PHP 5.4 or above, which is a huge improvement over PHP
version 5.2 that is required by Yii 1.1. As a result, there are many differences
on the language level that you should pay attention to. Below is a summary
of the major changes regarding PHP:
• Namespaces5.
• Anonymous functions6.
• Short array syntax [...elements...] is used instead of array(...elements
...).
• Short echo tags <?= are used in view files. This is safe to use starting
from PHP 5.4.
• SPL classes and interfaces7.
• Late Static Bindings8.
• Date and Time9.
• Traits10.
4
https://getcomposer.org/
5
http://php.net/manual/en/language.namespaces.php
6
http://php.net/manual/en/functions.anonymous.php
7
http://php.net/manual/en/book.spl.php
8
http://php.net/manual/en/language.oop5.late-static-bindings.php
9
http://php.net/manual/en/book.datetime.php
10
http://php.net/manual/en/language.oop5.traits.php
4 CHAPTER 1. INTRODUCTION
• intl11. Yii 2.0 makes use of the intl PHP extension to support inter-
nationalization features.
1.2.3 Namespace
The most obvious change in Yii 2.0 is the use of namespaces. Almost every
core class is namespaced, e.g., yiiwebRequest. The “C” prefix is no longer
used in class names. The naming scheme now follows the directory structure.
For example, yiiwebRequest indicates that the corresponding class file is web
/Request.php under the Yii framework folder.
(You can use any core class without explicitly including that class file,
thanks to the Yii class loader.)
1.2.4 Component and Object
Yii 2.0 breaks the CComponent class in 1.1 into two classes: yiibaseObject
and yiibaseComponent. The yiibaseObject class is a lightweight base
class that allows defining object properties via getters and setters. The yii
baseComponent class extends from yiibaseObject and supports events
and behaviors.
If your class does not need the event or behavior feature, you should
consider using yiibaseObject as the base class. This is usually the case
for classes that represent basic data structures.
1.2.5 Object Configuration
The yiibaseObject class introduces a uniform way of configuring objects.
Any descendant class of yiibaseObject should declare its constructor (if
needed) in the following way so that it can be properly configured:
class MyClass extends yiibaseObject
{
public function __construct($param1, $param2, $config = [])
{
// ... initialization before configuration is applied
parent::__construct($config);
}
public function init()
{
parent::init();
// ... initialization after configuration is applied
}
}
11
http://php.net/manual/en/book.intl.php
1.2. UPGRADING FROM VERSION 1.1 5
In the above, the last parameter of the constructor must take a configuration
array that contains name-value pairs for initializing the properties at the end
of the constructor. You can override the yiibaseObject::init() method
to do initialization work that should be done after the configuration has been
applied.
By following this convention, you will be able to create and configure
new objects using a configuration array:
$object = Yii::createObject([
’class’ => ’MyClass’,
’property1’ => ’abc’,
’property2’ => ’cde’,
], [$param1, $param2]);
More details about configurations can be found in the Object Configurations
section.
1.2.6 Events
In Yii 1, events were created by defining an on-method (e.g., onBeforeSave).
In Yii 2, you can now use any event name. You trigger an event by calling
the yiibaseComponent::trigger() method:
$event = new yiibaseEvent;
$component->trigger($eventName, $event);
To attach a handler to an event, use the yiibaseComponent::on() method:
$component->on($eventName, $handler);
// To detach the handler, use:
// $component->off($eventName, $handler);
There are many enhancements to the event features. For more details, please
refer to the Events section.
1.2.7 Path Aliases
Yii 2.0 expands the usage of path aliases to both file/directory paths and
URLs. Yii 2.0 also now requires an alias name to start with the @ character,
to differentiate aliases from normal file/directory paths or URLs. For ex-
ample, the alias @yii refers to the Yii installation directory. Path aliases are
supported in most places in the Yii core code. For example, yiicaching
FileCache::cachePath can take both a path alias and a normal directory
path.
A path alias is also closely related to a class namespace. It is recommen-
ded that a path alias be defined for each root namespace, thereby allowing
you to use Yii the class autoloader without any further configuration. For
example, because @yii refers to the Yii installation directory, a class like
yiiwebRequest can be autoloaded. If you use a third party library, such as
the Zend Framework, you may define a path alias @Zend that refers to that
6 CHAPTER 1. INTRODUCTION
framework’s installation directory. Once you’ve done that, Yii will be able
to autoload any class in that Zend Framework library, too.
More on path aliases can be found in the Path Aliases section.
1.2.8 Views
The most significant change about views in Yii 2 is that the special variable
$this in a view no longer refers to the current controller or widget. Instead,
$this now refers to a view object, a new concept introduced in 2.0. The view
object is of type yiiwebView, which represents the view part of the MVC
pattern. If you want to access the controller or widget in a view, you can
use $this->context.
To render a partial view within another view, you use $this->render(),
not $this->renderPartial(). The call to render also now has to be explicitly
echoed, as the render() method returns the rendering result, rather than
directly displaying it. For example:
echo $this->render(’_item’, [’item’ => $item]);
Besides using PHP as the primary template language, Yii 2.0 is also equipped
with official support for two popular template engines: Smarty and Twig.
The Prado template engine is no longer supported. To use these template
engines, you need to configure the view application component by setting
the yiibaseView::$renderers property. Please refer to the Template
Engines section for more details.
1.2.9 Models
Yii 2.0 uses yiibaseModel as the base model, similar to CModel in 1.1.
The class CFormModel has been dropped entirely. Instead, in Yii 2 you should
extend yiibaseModel to create a form model class.
Yii 2.0 introduces a new method called yiibaseModel::scenarios()
to declare supported scenarios, and to indicate under which scenario an
attribute needs to be validated, can be considered as safe or not, etc. For
example:
public function scenarios()
{
return [
’backend’ => [’email’, ’role’],
’frontend’ => [’email’, ’!role’],
];
}
In the above, two scenarios are declared: backend and frontend. For the
backend scenario, both the email and role attributes are safe, and can be
massively assigned. For the frontend scenario, email can be massively assigned
while role cannot. Both email and role should be validated using rules.
1.2. UPGRADING FROM VERSION 1.1 7
The yiibaseModel::rules() method is still used to declare the val-
idation rules. Note that due to the introduction of yiibaseModel::
scenarios(), there is no longer an unsafe validator.
In most cases, you do not need to override yiibaseModel::scenarios()
if the yiibaseModel::rules() method fully specifies the scenarios that
will exist, and if there is no need to declare unsafe attributes.
To learn more details about models, please refer to the Models section.
1.2.10 Controllers
Yii 2.0 uses yiiwebController as the base controller class, similar to
CWebController in Yii 1.1. yiibaseAction is the base class for action
classes.
The most obvious impact of these changes on your code is that a con-
troller action should return the content that you want to render instead of
echoing it:
public function actionView($id)
{
$model = appmodelsPost::findOne($id);
if ($model) {
return $this->render(’view’, [’model’ => $model]);
} else {
throw new yiiwebNotFoundHttpException;
}
}
Please refer to the Controllers section for more details about controllers.
1.2.11 Widgets
Yii 2.0 uses yiibaseWidget as the base widget class, similar to CWidget in
Yii 1.1.
To get better support for the framework in IDEs, Yii 2.0 introduces a new
syntax for using widgets. The static methods yiibaseWidget::begin(),
yiibaseWidget::end(), and yiibaseWidget::widget() have been in-
troduced, to be used like so:
use yiiwidgetsMenu;
use yiiwidgetsActiveForm;
// Note that you have to "echo" the result to display it
echo Menu::widget([’items’ => $items]);
// Passing an array to initialize the object properties
$form = ActiveForm::begin([
’options’ => [’class’ => ’form-horizontal’],
’fieldConfig’ => [’inputOptions’ => [’class’ => ’input-xlarge’]],
]);
... form input fields here ...
8 CHAPTER 1. INTRODUCTION
ActiveForm::end();
Please refer to the Widgets section for more details.
1.2.12 Themes
Themes work completely differently in 2.0. They are now based on a path
mapping mechanism that maps a source view file path to a themed view
file path. For example, if the path map for a theme is [’/web/views’ => ’/
web/themes/basic’], then the themed version for the view file /web/views/site
/index.php will be /web/themes/basic/site/index.php. For this reason, themes
can now be applied to any view file, even a view rendered outside of the
context of a controller or a widget.
Also, there is no more CThemeManager component. Instead, theme is a con-
figurable property of the view application component.
Please refer to the Theming section for more details.
1.2.13 Console Applications
Console applications are now organized as controllers, like Web applications.
Console controllers should extend from yiiconsoleController, similar
to CConsoleCommand in 1.1.
To run a console command, use yii <route>, where <route> stands for a
controller route (e.g. sitemap/index). Additional anonymous arguments are
passed as the parameters to the corresponding controller action method,
while named arguments are parsed according to the declarations in yii
consoleController::options().
Yii 2.0 supports automatic generation of command help information from
comment blocks.
Please refer to the Console Commands section for more details.
1.2.14 I18N
Yii 2.0 removes the built-in date formatter and number formatter pieces in
favor of the PECL intl PHP module12.
Message translation is now performed via the i18n application compon-
ent. This component manages a set of message sources, which allows you to
use different message sources based on message categories.
Please refer to the Internationalization section for more details.
1.2.15 Action Filters
Action filters are implemented via behaviors now. To define a new, custom
filter, extend from yiibaseActionFilter. To use a filter, attach the filter
12
http://pecl.php.net/package/intl
1.2. UPGRADING FROM VERSION 1.1 9
class to the controller as a behavior. For example, to use the yiifilters
AccessControl filter, you would have the following code in a controller:
public function behaviors()
{
return [
’access’ => [
’class’ => ’yiifiltersAccessControl’,
’rules’ => [
[’allow’ => true, ’actions’ => [’admin’], ’roles’ => [’@’]],
],
],
];
}
Please refer to the Filtering section for more details.
1.2.16 Assets
Yii 2.0 introduces a new concept called asset bundle that replaces the script
package concept found in Yii 1.1.
An asset bundle is a collection of asset files (e.g. JavaScript files, CSS
files, image files, etc.) within a directory. Each asset bundle is represented
as a class extending yiiwebAssetBundle. By registering an asset bundle
via yiiwebAssetBundle::register(), you make the assets in that bundle
accessible via the Web. Unlike in Yii 1, the page registering the bundle will
automatically contain the references to the JavaScript and CSS files specified
in that bundle.
Please refer to the Managing Assets section for more details.
1.2.17 Helpers
Yii 2.0 introduces many commonly used static helper classes, including.
• yiihelpersHtml
• yiihelpersArrayHelper
• yiihelpersStringHelper
• yiihelpersFileHelper
• yiihelpersJson
Please refer to the Helper Overview section for more details.
10 CHAPTER 1. INTRODUCTION
1.2.18 Forms
Yii 2.0 introduces the field concept for building a form using yiiwidgets
ActiveForm. A field is a container consisting of a label, an input, an er-
ror message, and/or a hint text. A field is represented as an yiiwidgets
ActiveField object. Using fields, you can build a form more cleanly than
before:
<?php $form = yiiwidgetsActiveForm::begin(); ?>
<?= $form->field($model, ’username’) ?>
<?= $form->field($model, ’password’)->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton(’Login’) ?>
</div>
<?php yiiwidgetsActiveForm::end(); ?>
Please refer to the Creating Forms section for more details.
1.2.19 Query Builder
In 1.1, query building was scattered among several classes, including CDbCommand
, CDbCriteria, and CDbCommandBuilder. Yii 2.0 represents a DB query in terms
of a yiidbQuery object that can be turned into a SQL statement with the
help of yiidbQueryBuilder behind the scene. For example:
$query = new yiidbQuery();
$query->select(’id, name’)
->from(’user’)
->limit(10);
$command = $query->createCommand();
$sql = $command->sql;
$rows = $command->queryAll();
Best of all, such query building methods can also be used when working with
Active Record.
Please refer to the Query Builder section for more details.
1.2.20 Active Record
Yii 2.0 introduces a lot of changes to Active Record. The two most obvious
ones involve query building and relational query handling.
The CDbCriteria class in 1.1 is replaced by yiidbActiveQuery in Yii 2.
That class extends from yiidbQuery, and thus inherits all query building
methods. You call yiidbActiveRecord::find() to start building a query:
// To retrieve all *active* customers and order them by their ID:
$customers = Customer::find()
->where([’status’ => $active])
->orderBy(’id’)
->all();
1.2. UPGRADING FROM VERSION 1.1 11
To declare a relation, simply define a getter method that returns an yiidb
ActiveQuery object. The property name defined by the getter represents
the relation name. For example, the following code declares an orders relation
(in 1.1, you would have to declare relations in a central place relations()):
class Customer extends yiidbActiveRecord
{
public function getOrders()
{
return $this->hasMany(’Order’, [’customer_id’ => ’id’]);
}
}
Now you can use $customer->orders to access a customer’s orders from the
related table. You can also use the following code to perform an on-the-fly
relational query with a customized query condition:
$orders = $customer->getOrders()->andWhere(’status=1’)->all();
When eager loading a relation, Yii 2.0 does it differently from 1.1. In partic-
ular, in 1.1 a JOIN query would be created to select both the primary and
the relational records. In Yii 2.0, two SQL statements are executed without
using JOIN: the first statement brings back the primary records and the
second brings back the relational records by filtering with the primary keys
of the primary records.
Instead of returning yiidbActiveRecord objects, you may chain the
yiidbActiveQuery::asArray() method when building a query to return
a large number of records. This will cause the query result to be returned
as arrays, which can significantly reduce the needed CPU time and memory
if large number of records . For example:
$customers = Customer::find()->asArray()->all();
Another change is that you can’t define attribute default values through
public properties anymore. If you need those, you should set them in the
init method of your record class.
public function init()
{
parent::init();
$this->status = self::STATUS_NEW;
}
There were some problems with overriding the constructor of an ActiveRecord
class in 1.1. These are not present in version 2.0 anymore. Note that when
adding parameters to the constructor you might have to override yiidb
ActiveRecord::instantiate().
There are many other changes and enhancements to Active Record.
Please refer to the Active Record section for more details.
12 CHAPTER 1. INTRODUCTION
1.2.21 Active Record Behaviors
In 2.0, we have dropped the base behavior class CActiveRecordBehavior. If you
want to create an Active Record Behavior, you will have to extend directly
from yiibaseBehavior. If the behavior class needs to respond to some events
of the owner, you have to override the events() method like the following,
namespace appcomponents;
use yiidbActiveRecord;
use yiibaseBehavior;
class MyBehavior extends Behavior
{
// ...
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => ’beforeValidate’,
];
}
public function beforeValidate($event)
{
// ...
}
}
1.2.22 User and IdentityInterface
The CWebUser class in 1.1 is now replaced by yiiwebUser, and there is
no more CUserIdentity class. Instead, you should implement the yiiweb
IdentityInterface which is much more straightforward to use. The ad-
vanced application template provides such an example.
Please refer to the Authentication, Authorization, and Advanced Applic-
ation Technique sections for more details.
1.2.23 URL Management
URL management in Yii 2 is similar to that in 1.1. A major enhancement
is that URL management now supports optional parameters. For example,
if you have a rule declared as follows, then it will match both post/popular
and post/1/popular. In 1.1, you would have had to use two rules to achieve
the same goal.
[
’pattern’ => ’post/<page:d+>/<tag>’,
’route’ => ’post/index’,
’defaults’ => [’page’ => 1],
]
1.2. UPGRADING FROM VERSION 1.1 13
Please refer to the Url manager docs section for more details.
1.2.24 Using Yii 1.1 and 2.x together
If you have legacy Yii 1.1 code that you want to use together with Yii 2.0,
please refer to the Using Yii 1.1 and 2.0 Together section.
14 CHAPTER 1. INTRODUCTION
Chapter 2
Getting Started
2.1 Installing Yii
You can install Yii in two ways, using Composer1 or by downloading an
archive file. The former is the preferred way, as it allows you to install new
extensions or update Yii by simply running a single command.
Note: Unlike with Yii 1, standard installations of Yii 2 result
in both, the framework and an application skeleton being down-
loaded and installed.
2.1.1 Installing via Composer
If you do not already have Composer installed, you may do so by following
the instructions at getcomposer.org2. On Linux and Mac OS X, you’ll run
the following commands:
curl -s http://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
On Windows, you’ll download and run Composer-Setup.exe3.
Please refer to the Composer Documentation4 if you encounter any prob-
lems or want to learn more about Composer usage.
With Composer installed, you can install Yii by running the following
commands under a Web-accessible folder:
composer global require "fxp/composer-asset-plugin:1.0.0-beta2"
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
The first command installs the composer asset plugin5 which allows man-
aging bower and npm package dependencies through Composer. You only
1
http://getcomposer.org/
2
https://getcomposer.org/download/
3
https://getcomposer.org/Composer-Setup.exe
4
https://getcomposer.org/doc/
5
https://github.com/francoispluchino/composer-asset-plugin/
15
16 CHAPTER 2. GETTING STARTED
need to run this command once for all. The second command installs Yii in
a directory named basic.
Tip: If you want to install the latest development version of
Yii, you may use the following command instead, which adds a
stability option6:
composer create-project --prefer-dist --stability=dev yiisoft/
yii2-app-basic basic
Note that the development version of Yii should not be used for
production as it may break your running code.
2.1.2 Installing from an Archive File
Installing Yii from an archive file involves three steps:
1. Download the archive file from yiiframework.com7.
2. Unpack the downloaded file to a Web-accessible folder.
3. Modify the config/web.php file by entering a secret key for the cookieValidationKey
configuration item (this is done automatically if you are installing Yii
using Composer):
// !!! insert a secret key in the following (if it is empty) - this is
required by cookie validation
’cookieValidationKey’ => ’enter your secret key here’,
2.1.3 Other Installation Options
The above installation instructions show how to install Yii, which also creates
a basic Web application that works out of the box. This approach is a good
starting point for small projects, or for when you just start learning Yii.
But there are other installation options available:
• If you only want to install the core framework and would like to build
an entire application from scratch, you may follow the instructions as
explained in Building Application from Scratch.
• If you want to start with a more sophisticated application, better suited
to team development environments, you may consider installing the
Advanced Application Template.
6
https://getcomposer.org/doc/04-schema.md#minimum-stability
7
https://github.com/yiisoft/yii2/releases/download/2.0.0-rc/
yii-basic-app-2.0.0-rc.tgz
2.1. INSTALLING YII 17
2.1.4 Verifying the Installation
After installation, you can use your browser to access the installed Yii ap-
plication with the following URL:
http://localhost/basic/web/index.php
This URL assumes you have installed Yii in a directory named basic, directly
under the Web server’s document root directory, and that the Web server
is running on your local machine (localhost). You may need to adjust it to
your installation environment.
You should see the above “Congratulations!“ page in your browser. If
not, please check if your PHP installation satisfies Yii’s requirements. You
can check if the minimum requirements are met using one of the following
approaches:
• Use a browser to access the URL http://localhost/basic/requirements.
php
• Run the following commands:
cd basic
php requirements.php
You should configure your PHP installation so that it meets the minimum
requirements of Yii. Most importantly, you should have PHP 5.4 or above.
You should also install the PDO PHP Extension8 and a corresponding data-
base driver (such as pdo_mysql for MySQL databases), if your application
needs a database.
8
http://www.php.net/manual/en/pdo.installation.php
18 CHAPTER 2. GETTING STARTED
2.1.5 Configuring Web Servers
Info: You may skip this subsection for now if you are just test
driving Yii with no intention of deploying it to a production
server.
The application installed according to the above instructions should work out
of box with either an Apache HTTP server9 or an Nginx HTTP server10, on
Windows, Mac OS X, or Linux running PHP 5.4 or higher. Yii 2.0 is also
compatible the facebooks HHVM11 however there are some edge cases where
HHVM behaves different than native PHP so you have to take some extra
care when using HHVM.
On a production server, you may want to configure your Web server
so that the application can be accessed via the URL http://www.example.com
/index.php instead of http://www.example.com/basic/web/index.php. Such con-
figuration requires pointing the document root of your Web server to the
basic/web folder. You may also want to hide index.php from the URL, as
described in the URL Parsing and Generation section. In this subsection,
you’ll learn how to configure your Apache or Nginx server to achieve these
goals.
Info: By setting basic/web as the document root, you also prevent
end users from accessing your private application code and sensit-
ive data files that are stored in the sibling directories of basic/web.
Denying access to those other folders is a security improvement.
Info: If your application will run in a shared hosting environment
where you do not have permission to modify its Web server con-
figuration, you may still adjust the structure of your application
for better security. Please refer to the Shared Hosting Environ-
ment section for more details.
Recommended Apache Configuration
Use the following configuration in Apache’s httpd.conf file or within a virtual
host configuration. Note that you should replace path/to/basic/web with the
actual path for basic/web.
# Set document root to be "basic/web"
DocumentRoot "path/to/basic/web"
<Directory "path/to/basic/web">
# use mod_rewrite for pretty URL support
RewriteEngine on
9
http://httpd.apache.org/
10
http://nginx.org/
11
http://hhvm.com/
2.1. INSTALLING YII 19
# If a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Otherwise forward the request to index.php
RewriteRule . index.php
# ...other settings...
</Directory>
Recommended Nginx Configuration
You should have installed PHP as an FPM SAPI12 to use Nginx13. Use the
following Nginx configuration, replacing path/to/basic/web with the actual
path for basic/web and mysite.local with the actual hostname to serve.
server {
charset utf-8;
client_max_body_size 128M;
listen 80; ## listen for ipv4
#listen [::]:80 default_server ipv6only=on; ## listen for ipv6
server_name mysite.local;
root /path/to/basic/web;
index index.php;
access_log /path/to/basic/log/access.log main;
error_log /path/to/basic/log/error.log;
location / {
# Redirect everything that isn’t a real file to index.php
try_files $uri $uri/ /index.php?$args;
}
# uncomment to avoid processing of calls to non-existing static files by
Yii
#location ~ .(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
# try_files $uri =404;
#}
#error_page 404 /404.html;
location ~ .php$ {
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
#fastcgi_pass unix:/var/run/php5-fpm.sock;
try_files $uri =404;
}
location ~ /.(ht|svn|git) {
deny all;
12
http://php.net/install.fpm
13
http://wiki.nginx.org/
20 CHAPTER 2. GETTING STARTED
}
}
When using this configuration, you should also set cgi.fix_pathinfo=0 in the
php.ini file in order to avoid many unnecessary system stat() calls.
Also note that when running an HTTPS server, you need to add fastcgi_param
HTTPS on; so that Yii can properly detect if a connection is secure.
2.2 Running Applications
After installing Yii, you have a working Yii application that can be ac-
cessed via the URL http://hostname/basic/web/index.php or http://hostname/
index.php, depending upon your configuration. This section will introduce
the application’s built-in functionality, how the code is organized, and how
the application handles requests in general.
Info: For simplicity, throughout this “Getting Started” tutorial,
it’s assumed that you have set basic/web as the document root
of your Web server, and configured, the URL for accessing your
application to be http://hostname/index.php or something similar.
For your needs, please adjust the URLs in our descriptions ac-
cordingly.
2.2.1 Functionality
The basic application installed contains four pages:
• The homepage, displayed when you access the URL http://hostname/
index.php,
• the “About” page,
• the “Contact” page, which displays a contact form that allows end users
to contact you via email,
• and the “Login” page, which displays a login form that can be used to
authenticate end users. Try logging in with “admin/admin”, and you
will find the “Login” main menu item will change to “Logout”.
These pages share a common header and footer. The header contains a main
menu bar to allow navigation among different pages.
You should also see a toolbar at the bottom of the browser window.
This is a useful debugger tool provided by Yii to record and display a lot of
debugging information, such as log messages, response statuses, the database
queries run, and so on.
2.2. RUNNING APPLICATIONS 21
2.2.2 Application Structure
The most important directories and files in your application are (assuming
the application’s root directory is basic):
basic/ application base path
composer.json used by Composer, describes package information
config/ contains application and other configurations
console.php the console application configuration
web.php the Web application configuration
commands/ contains console command classes
controllers/ contains controller classes
models/ contains model classes
runtime/ contains files generated by Yii during runtime, such
as logs and cache files
vendor/ contains the installed Composer packages, including
the Yii framework itself
views/ contains view files
web/ application Web root, contains Web accessible files
assets/ contains published asset files (javascript and css)
by Yii
index.php the entry (or bootstrap) script for the application
yii the Yii console command execution script
In general, the files in the application can be divided into two types: those
under basic/web and those under other directories. The former can be directly
accessed via HTTP (i.e., in a browser), while the latter can not and should
not be.
Yii implements the model-view-controller (MVC)14 design pattern, which
is reflected in the above directory organization. The models directory con-
tains all model classes, the views directory contains all view scripts, and the
controllers directory contains all controller classes.
The following diagram shows the static structure of an application.
14
http://wikipedia.org/wiki/Model-view-controller
22 CHAPTER 2. GETTING STARTED
Each application has an entry script web/index.php which is the only Web
accessible PHP script in the application. The entry script takes an incoming
request and creates an application instance to handle it. The application
resolves the request with the help of its components, and dispatches the
request to the MVC elements. Widgets are used in the views to help build
complex and dynamic user interface elements.
2.2.3 Request Lifecycle
The following diagram shows how an application handles a request.
2.2. RUNNING APPLICATIONS 23
1. A user makes a request to the entry script web/index.php.
2. The entry script loads the application configuration and creates an
application instance to handle the request.
3. The application resolves the requested route with the help of the re-
quest application component.
4. The application creates a controller instance to handle the request.
5. The controller creates an action instance and performs the filters for
the action.
6. If any filter fails, the action is cancelled.
7. If all filters pass, the action is executed.
8. The action loads a data model, possibly from a database.
9. The action renders a view, providing it with the data model.
10. The rendered result is returned to the response application component.
11. The response component sends the rendered result to the user’s browser.
24 CHAPTER 2. GETTING STARTED
2.3 Saying Hello
This section describes how to create a new “Hello” page in your application.
To achieve this goal, you will create an action and a view:
• The application will dispatch the page request to the action
• and the action will in turn render the view that shows the word “Hello”
to the end user.
Through this tutorial, you will learn three things:
1. How to create an action to respond to requests,
2. how to create a view to compose the response’s content, and
3. how an application dispatches requests to actions.
2.3.1 Creating an Action
For the “Hello” task, you will create a say action that reads a message para-
meter from the request and displays that message back to the user. If the
request does not provide a message parameter, the action will display the
default “Hello” message.
Info: Actions are the objects that end users can directly refer to
for execution. Actions are grouped by controllers. The execution
result of an action is the response that an end user will receive.
Actions must be declared in controllers. For simplicity, you may declare the
say action in the existing SiteController. This controller is defined in the
class file controllers/SiteController.php. Here is the start of the new action:
<?php
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
// ...existing code...
public function actionSay($message = ’Hello’)
{
return $this->render(’say’, [’message’ => $message]);
}
}
2.3. SAYING HELLO 25
In the above code, the say action is defined as a method named actionSay
in the SiteController class. Yii uses the prefix action to differentiate action
methods from non-action methods in a controller class. The name after the
action prefix maps to the action’s ID.
When it comes to naming your actions, you should understand how Yii
treats action IDs. Action IDs are always referenced in lower case. If an
action ID requires multiple words, they will be concatenated by dashes (e.g.,
create-comment). Action method names are mapped to action IDs by remov-
ing any dashes from the IDs, capitalizing the first letter in each word, and
prefixing the resulting with action. For example, the action ID create-comment
corresponds to the action method name actionCreateComment.
The action method in our example takes a parameter $message, whose
value defaults to "Hello" (in exactly the same way you set a default value for
any function or method argument in PHP). When the application receives a
request and determines that the say action is responsible for handling said
request, the application will populate this parameter with the same named
parameter found in the request. In other words, if the request includes a
message parameter with a value of "Goodbye", the $message variable within the
action will be assigned that value.
Within the action method, yiiwebController::render() is called to
render a view file named say. The message parameter is also passed to the
view so that it can be used there. The rendering result is returned by the
action method. That result will be received by the application and displayed
to the end user in the browser (as part of a complete HTML page).
2.3.2 Creating a View
Views are scripts you write to generate a response’s content. For the “Hello”
task, you will create a say view that prints the message parameter received
from the action method, and passed by the action to the view:
<?php
use yiihelpersHtml;
?>
<?= Html::encode($message) ?>
The say view should be saved in the file views/site/say.php. When the method
yiiwebController::render() is called in an action, it will look for a PHP
file named as views/ControllerID/ViewName.php.
Note that in the above code, the message parameter is yiihelpersHtml
::encode() before being printed. This is necessary as the parameter comes
from an end user, making it vulnerable to cross-site scripting (XSS) attacks15
by embedding malicious JavaScript code in the parameter.
Naturally, you may put more content in the say view. The content can
consist of HTML tags, plain text, and even PHP statements. In fact, the say
15
http://en.wikipedia.org/wiki/Cross-site_scripting
26 CHAPTER 2. GETTING STARTED
view is just a PHP script that is executed by the yiiwebController::
render() method. The content printed by the view script will be returned to
the application as the response’s result. The application will in turn output
this result to the end user.
2.3.3 Trying it Out
After creating the action and the view, you may access the new page by
accessing the following URL:
http://hostname/index.php?r=site/say&message=Hello+World
This URL will result in a page displaying “Hello World”. The page shares
the same header and footer as the other application pages.
If you omit the message parameter in the URL, you would see the page
display just “Hello”. This is because message is passed as a parameter to the
actionSay() method, and when it is omitted, the default value of "Hello" will
be used instead.
Info: The new page shares the same header and footer as other
pages because the yiiwebController::render() method will
automatically embed the result of the say view in a so-called
layout which in this case is located at views/layouts/main.php.
The r parameter in the above URL requires more explanation. It stands for
route, an application wide unique ID that refers to an action. The route’s
format is ControllerID/ActionID. When the application receives a request, it
will check this parameter, using the ControllerID part to determine which
2.4. WORKING WITH FORMS 27
controller class should be instantiated to handle the request. Then, the
controller will use the ActionID part to determine which action should be
instantiated to do the real work. In this example case, the route site/say
will be resolved to the SiteController controller class and the say action. As
a result, the SiteController::actionSay() method will be called to handle the
request.
Info: Like actions, controllers also have IDs that uniquely identify
them in an application. Controller IDs use the same naming
rules as action IDs. Controller class names are derived from
controller IDs by removing dashes from the IDs, capitalizing the
first letter in each word, and suffixing the resulting string with
the word Controller. For example, the controller ID post-comment
corresponds to the controller class name PostCommentController.
2.3.4 Summary
In this section, you have touched the controller and view parts of the MVC
design pattern. You created an action as part of a controller to handle a
specific request. And you also created a view to compose the response’s
content. In this simple example, no model was involved as the only data
used was the message parameter.
You have also learned about routes in Yii, which act as the bridge between
user requests and controller actions.
In the next section, you will learn how to create a model, and add a new
page containing an HTML form.
2.4 Working with Forms
This section describes how to create a new page with a form for getting data
from users. The page will display a form with a name input field and an
email input field. After getting those two pieces of information from the
user, the page will echo the entered values back for confirmation.
To achieve this goal, besides creating an action and two views, you will
also create a model.
Through this tutorial, you will learn how to:
• Create a model to represent the data entered by a user through a form
• Declare rules to validate the data entered
• Build an HTML form in a view
28 CHAPTER 2. GETTING STARTED
2.4.1 Creating a Model
The data to be requested from the user will be represented by an EntryForm
model class as shown below and saved in the file models/EntryForm.php. Please
refer to the Class Autoloading section for more details about the class file
naming convention.
<?php
namespace appmodels;
use yiibaseModel;
class EntryForm extends Model
{
public $name;
public $email;
public function rules()
{
return [
[[’name’, ’email’], ’required’],
[’email’, ’email’],
];
}
}
The class extends from yiibaseModel, a base class provided by Yii, com-
monly used to represent form data.
Info: yiibaseModel is used as a parent for model classes not
associated with database tables. yiidbActiveRecord is nor-
mally the parent for model classes that do correspond to database
tables.
The EntryForm class contains two public members, name and email, which are
used to store the data entered by the user. It also contains a method named
rules(), which returns a set of rules for validating the data. The validation
rules declared above state that
• both the name and email values are required
• the email data must be a syntactically valid email address
If you have an EntryForm object populated with the data entered by a user,
you may call its yiibaseModel::validate() to trigger the data validation
routines. A data validation failure will set the yiibaseModel::hasErrors
property to true, and you may learn what validation errors occurred through
yiibaseModel::getErrors.
2.4. WORKING WITH FORMS 29
<?php
$model = new EntryForm();
$model->name = ’Qiang’;
$model->email = ’bad’;
if ($model->validate()) {
// Good!
} else {
// Failure!
// Use $model->getErrors()
}
2.4.2 Creating an Action
Next, you’ll need to create an entry action in the site controller that will use
the new model. The process of creating and using actions was explained in
the Saying Hello section.
<?php
namespace appcontrollers;
use Yii;
use yiiwebController;
use appmodelsEntryForm;
class SiteController extends Controller
{
// ...existing code...
public function actionEntry()
{
$model = new EntryForm;
if ($model->load(Yii::$app->request->post()) && $model->validate())
{
// valid data received in $model
// do something meaningful here about $model ...
return $this->render(’entry-confirm’, [’model’ => $model]);
} else {
// either the page is initially displayed or there is some
validation error
return $this->render(’entry’, [’model’ => $model]);
}
}
}
The action first creates an EntryForm object. It then tries to populate the
model with the data from $_POST, provided in Yii by yiiwebRequest::
post(). If the model is successfully populated (i.e., if the user has submitted
the HTML form), the action will call yiibaseModel::validate() to make
30 CHAPTER 2. GETTING STARTED
sure the values entered are valid.
Info: The expression Yii::$app represents the application instance,
which is a globally accessible singleton. It is also a service loc-
ator that provides components such as request, response, db, etc.
to support specific functionality. In the above code, the request
component of the application instance is used to access the $_POST
data.
If everything is fine, the action will render a view named entry-confirm to
confirm the successful submission of the data to the user. If no data is sub-
mitted or the data contains errors, the entry view will be rendered, wherein
the HTML form will be shown, along with any validation error messages.
Note: In this very simple example we just render the confirmation
page upon valid data submission. In practice, you should con-
sider using yiiwebController::refresh() or yiiwebController
::redirect() to avoid form resubmission problems16.
2.4.3 Creating Views
Finally, create two view files named entry-confirm and entry. These will be
rendered by the entry action, as just described.
The entry-confirm view simply displays the name and email data. It
should be stored in the file views/site/entry-confirm.php.
<?php
use yiihelpersHtml;
?>
<p>You have entered the following information:</p>
<ul>
<li><label>Name</label>: <?= Html::encode($model->name) ?></li>
<li><label>Email</label>: <?= Html::encode($model->email) ?></li>
</ul>
The entry view displays an HTML form. It should be stored in the file
views/site/entry.php.
<?php
use yiihelpersHtml;
use yiiwidgetsActiveForm;
?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, ’name’) ?>
<?= $form->field($model, ’email’) ?>
16
http://en.wikipedia.org/wiki/Post/Redirect/Get
2.4. WORKING WITH FORMS 31
<div class="form-group">
<?= Html::submitButton(’Submit’, [’class’ => ’btn btn-primary’]) ?>
</div>
<?php ActiveForm::end(); ?>
The view uses a powerful widget called yiiwidgetsActiveForm to build
the HTML form. The begin() and end() methods of the widget render the
opening and closing form tags, respectively. Between the two method calls,
input fields are created by the yiiwidgetsActiveForm::field() method.
The first input field is for the “name” data, and the second for the “email”
data. After the input fields, the yiihelpersHtml::submitButton() method
is called to generate a submit button.
2.4.4 Trying it Out
To see how it works, use your browser to access the following URL:
http://hostname/index.php?r=site/entry
You will see a page displaying a form with two input fields. In front of
each input field, a label indicates what data is to be entered. If you click
the submit button without entering anything, or if you do not provide a
valid email address, you will see an error message displayed next to each
problematic input field.
After entering a valid name and email address and clicking the submit
button, you will see a new page displaying the data that you just entered.
32 CHAPTER 2. GETTING STARTED
Magic Explained
You may wonder how the HTML form works behind the scene, because it
seems almost magical that it can display a label for each input field and show
error messages if you do not enter the data correctly without reloading the
page.
Yes, the data validation is initially done on the client side using JavaS-
cript, and secondarily performed on the server side via PHP. yiiwidgets
ActiveForm is smart enough to extract the validation rules that you have
declared in EntryForm, turn them into executable JavaScript code, and use
the JavaScript to perform data validation. In case you have disabled JavaS-
cript on your browser, the validation will still be performed on the server
side, as shown in the actionEntry() method. This ensures data validity in all
circumstances.
Warning: Client-side validation is a convenience that provides
for a better user experience. Server-side validation is always re-
quired, whether or not client-side validation is in place.
The labels for input fields are generated by the field() method, using the
property names from the model. For example, the label Name will be generated
for the name property.
You may customize a label within a view using the following code:
<?= $form->field($model, ’name’)->label(’Your Name’) ?>
<?= $form->field($model, ’email’)->label(’Your Email’) ?>
2.5. WORKING WITH DATABASES 33
Info: Yii provides many such widgets to help you quickly build
complex and dynamic views. As you will learn later, writing
a new widget is also extremely easy. You may want to turn
much of your view code into reusable widgets to simplify view
development in future.
2.4.5 Summary
In this section of the guide, you have touched every part in the MVC design
pattern. You have learned how to create a model class to represent the user
data and validate said data.
You have also learned how to get data from users and how to display
data back in the browser. This is a task that could take you a lot of time
when developing an application, but Yii provides powerful widgets to make
this task very easy.
In the next section, you will learn how to work with databases, which
are needed in nearly every application.
2.5 Working with Databases
This section will describe how to create a new page that displays country
data fetched from a database table named country. To achieve this goal, you
will configure a database connection, create an Active Record class, define
an action, and create a view.
Through this tutorial, you will learn how to:
• Configure a DB connection
• Define an Active Record class
• Query data using the Active Record class
• Display data in a view in a paginated fashion
Note that in order to finish this section, you should have basic knowledge and
experience using databases. In particular, you should know how to create a
database, and how to execute SQL statements using a DB client tool.
2.5.1 Preparing the Database
To begin, create a database named yii2basic, from which you will fetch
data in your application. You may create an SQLite, MySQL, PostgreSQL,
MSSQL or Oracle database, as Yii has built-in support for many database
applications. For simplicity, MySQL will be assumed in the following de-
scription.
34 CHAPTER 2. GETTING STARTED
Next, create a table named country in the database, and insert some
sample data. You may run the following SQL statements to do so:
CREATE TABLE ‘country‘ (
‘code‘ CHAR(2) NOT NULL PRIMARY KEY,
‘name‘ CHAR(52) NOT NULL,
‘population‘ INT(11) NOT NULL DEFAULT ’0’
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO ‘country‘ VALUES (’AU’,’Australia’,18886000);
INSERT INTO ‘country‘ VALUES (’BR’,’Brazil’,170115000);
INSERT INTO ‘country‘ VALUES (’CA’,’Canada’,1147000);
INSERT INTO ‘country‘ VALUES (’CN’,’China’,1277558000);
INSERT INTO ‘country‘ VALUES (’DE’,’Germany’,82164700);
INSERT INTO ‘country‘ VALUES (’FR’,’France’,59225700);
INSERT INTO ‘country‘ VALUES (’GB’,’United Kingdom’,59623400);
INSERT INTO ‘country‘ VALUES (’IN’,’India’,1013662000);
INSERT INTO ‘country‘ VALUES (’RU’,’Russia’,146934000);
INSERT INTO ‘country‘ VALUES (’US’,’United States’,278357000);
At this point, you have a database named yii2basic, and within it a country
table with three columns, containing ten rows of data.
2.5.2 Configuring a DB Connection
Before proceeding, make sure you have installed both the PDO17 PHP ex-
tension and the PDO driver for the database you are using (e.g. pdo_mysql
for MySQL). This is a basic requirement if your application uses a relational
database.
With those installed, open the file config/db.php and change the para-
meters to be correct for your database. By default, the file contains the
following:
<?php
return [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=yii2basic’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
];
The config/db.php file is a typical file-based configuration tool. This particu-
lar configuration file specifies the parameters needed to create and initialize
a yiidbConnection instance through which you can make SQL queries
against the underlying database.
The DB connection configured above can be accessed in the application
code via the expression Yii::$app->db.
17
http://www.php.net/manual/en/book.pdo.php
2.5. WORKING WITH DATABASES 35
Info: The config/db.php file will be included by the main applica-
tion configuration config/web.php, which specifies how the applic-
ation instance should be initialized. For more information, please
refer to the Configurations section.
2.5.3 Creating an Active Record
To represent and fetch the data in the country table, create an Active Record-
derived class named Country, and save it in the file models/Country.php.
<?php
namespace appmodels;
use yiidbActiveRecord;
class Country extends ActiveRecord
{
}
The Country class extends from yiidbActiveRecord. You do not need to
write any code inside of it! With just the above code, Yii will guess the
associated table name from the class name.
Info: If no direct match can be made from the class name to
the table name, you can override the yiidbActiveRecord::
tableName() method to explicitly specify the associated table
name.
Using the Country class, you can easily manipulate data in the country table,
as shown in these snippets:
use appmodelsCountry;
// get all rows from the country table and order them by "name"
$countries = Country::find()->orderBy(’name’)->all();
// get the row whose primary key is "US"
$country = Country::findOne(’US’);
// displays "United States"
echo $country->name;
// modifies the country name to be "U.S.A." and save it to database
$country->name = ’U.S.A.’;
$country->save();
Info: Active Record is a powerful way to access and manipulate
database data in an object-oriented fashion. You may find more
detailed information in the Active Record section. Alternatively,
you may also interact with a database using a lower-level data
accessing method called Data Access Objects.
36 CHAPTER 2. GETTING STARTED
2.5.4 Creating an Action
To expose the country data to end users, you need to create a new action.
Instead of placing the new action in the site controller, like you did in the
previous sections, it makes more sense to create a new controller specific-
ally for all actions related to the country data. Name this new controller
CountryController, and create an index action in it, as shown in the following.
<?php
namespace appcontrollers;
use yiiwebController;
use yiidataPagination;
use appmodelsCountry;
class CountryController extends Controller
{
public function actionIndex()
{
$query = Country::find();
$pagination = new Pagination([
’defaultPageSize’ => 5,
’totalCount’ => $query->count(),
]);
$countries = $query->orderBy(’name’)
->offset($pagination->offset)
->limit($pagination->limit)
->all();
return $this->render(’index’, [
’countries’ => $countries,
’pagination’ => $pagination,
]);
}
}
Save the above code in the file controllers/CountryController.php.
The index action calls Country::find(). This Active Record method builds
a DB query and retrieves all of the data from the country table. To limit the
number of countries returned in each request, the query is paginated with
the help of a yiidataPagination object. The Pagination object serves two
purposes:
• Sets the offset and limit clauses for the SQL statement represented
by the query so that it only returns a single page of data at a time (at
most 5 rows in a page).
• It’s used in the view to display a pager consisting of a list of page
buttons, as will be explained in the next subsection.
2.5. WORKING WITH DATABASES 37
At the end of the code, the index action renders a view named index, and
passes the country data as well as the pagination information to it.
2.5.5 Creating a View
Under the views directory, first create a sub-directory named country. This
folder will be used to hold all the views rendered by the country controller.
Within the views/country directory, create a file named index.php containing
the following:
<?php
use yiihelpersHtml;
use yiiwidgetsLinkPager;
?>
<h1>Countries</h1>
<ul>
<?php foreach ($countries as $country): ?>
<li>
<?= Html::encode("{$country->name} ({$country->code})") ?>:
<?= $country->population ?>
</li>
<?php endforeach; ?>
</ul>
<?= LinkPager::widget([’pagination’ => $pagination]) ?>
The view has two sections relative to displaying the country data. In the
first part, the provided country data is traversed and rendered as an un-
ordered HTML list. In the second part, a yiiwidgetsLinkPager widget
is rendered using the pagination information passed from the action. The
LinkPager widget displays a list of page buttons. Clicking on any of them will
refresh the country data in the corresponding page.
2.5.6 Trying it Out
To see how all of the above code works, use your browser to access the
following URL:
http://hostname/index.php?r=country/index
38 CHAPTER 2. GETTING STARTED
At first, you will see a page showing five countries. Below the countries,
you will see a pager with four buttons. If you click on the button “2”, you
will see the page display another five countries in the database: the second
page of records. Observe more carefully and you will find that the URL in
the browser also changes to
http://hostname/index.php?r=country/index&page=2
Behind the scenes, yiidataPagination is providing all of the necessary
functionality to paginate a data set:
• Initially, yiidataPagination represents the first page, which reflects
the country SELECT query with the clause LIMIT 5 OFFSET 0. As a
result, the first five countries will be fetched and displayed.
• The yiiwidgetsLinkPager widget renders the page buttons using
the URLs created by yiidataPagination::createUrl(). The URLs
will contain the query parameter page, which represents the different
page numbers.
• If you click the page button “2”, a new request for the route country/
index will be triggered and handled. yiidataPagination reads the
page query parameter from the URL and sets the current page number
to 2. The new country query will thus have the clause LIMIT 5 OFFSET 5
and return the next five countries for display.
2.6. GENERATING CODE WITH GII 39
2.5.7 Summary
In this section, you learned how to work with a database. You also learned
how to fetch and display data in pages with the help of yiidataPagination
and yiiwidgetsLinkPager.
In the next section, you will learn how to use the powerful code gen-
eration tool, called Gii, to help you rapidly implement some commonly re-
quired features, such as the Create-Read-Update-Delete (CRUD) operations
for working with the data in a database table. As a matter of fact, the code
you have just written can all be automatically generated in Yii using the Gii
tool.
2.6 Generating Code with Gii
This section will describe how to use Gii to automatically generate code that
implements some common Web site features. Using Gii to auto-generate code
is simply a matter of entering the right information per to the instructions
shown on the Gii Web pages.
Through this tutorial, you will learn how to:
• Enable Gii in your application
• Use Gii to generate an Active Record class
• Use Gii to generate the code implementing the CRUD operations for
a DB table
• Customize the code generated by Gii
2.6.1 Starting Gii
Gii is provided in Yii as a module. You can enable Gii by configuring it in the
yiibaseApplication::modules property of the application. Depending
upon how you created your application, you may find the following code is
already provided in the config/web.php configuration file:
$config = [ ... ];
if (YII_ENV_DEV) {
$config[’bootstrap’][] = ’gii’;
$config[’modules’][’gii’] = ’yiigiiModule’;
}
The above configuration states that when in development environment, the
application should include a module named gii, which is of class yiigii
Module.
If you check the entry script web/index.php of your application, you will
find the following line, which essentially makes YII_ENV_DEV to be true.
40 CHAPTER 2. GETTING STARTED
defined(’YII_ENV’) or define(’YII_ENV’, ’dev’);
Thanks to that line, your application is in development mode, and will have
already enabled Gii, per the above configuration. You can now access Gii
via the following URL:
http://hostname/index.php?r=gii
Note: If you are accessing Gii from a machine other than local-
host, the access will be denied by default for security purpose.
You can configure Gii to add the allowed IP addresses as follows,
’gii’ => [
’class’ => ’yiigiiModule’,
’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’
192.168.178.20’] // adjust this to your needs
],
2.6.2 Generating an Active Record Class
To use Gii to generate an Active Record class, select the “Model Generator”
(by clicking the link on the Gii index page). Then fill out the form as follows:
• Table Name: country
• Model Class: Country
2.6. GENERATING CODE WITH GII 41
Next, click on the “Preview” button. You will see models/Country.php is
listed in the resulting class file to be created. You may click on the name of
the class file to preview its content.
When using Gii, if you have already created the same file and would be
overwriting it, click the diff button next to the file name to see the differences
between the code to be generated and the existing version.
42 CHAPTER 2. GETTING STARTED
When overwriting an existing file, check the box next to “overwrite” and
then click the “Generate” button. If creating a new file, you can just click
“Generate”.
Next, you will see a confirmation page indicating the code has been
successfully generated. If you had an existing file, you’ll also see a message
indicating that it was overwritten with the newly generated code.
2.6.3 Generating CRUD Code
CRUD stands for Create, Read, Update, and Delete, representing the four
common tasks taken with data on most Web sites. To create CRUD func-
tionality using Gii, select the “CRUD Generator” (by clicking the link on the
Gii index page). For the “country” example, fill out the resulting form as
follows:
• Model Class: appmodelsCountry
• Search Model Class: appmodelsCountrySearch
• Controller Class: appcontrollersCountryController
2.6. GENERATING CODE WITH GII 43
Next, click on the “Preview” button. You will see a list of files to be
generated, as shown below.
If you previously created the controllers/CountryController.php and views
/country/index.php files (in the databases section of the guide), check the
“overwrite” box to replace them. (The previous versions did not have full
CRUD support.)
44 CHAPTER 2. GETTING STARTED
2.6.4 Trying it Out
To see how it works, use your browser to access the following URL:
http://hostname/index.php?r=country/index
You will see a data grid showing the countries from the database table. You
may sort the grid, or filter it by entering filter conditions in the column
headers.
For each country displayed in the grid, you may choose to view its details,
update it, or delete it. You may also click on the “Create Country” button
on top of the grid to be provided with a form for creating a new country.
2.7. LOOKING AHEAD 45
The following is the list of the files generated by Gii, in case you want to
investigate how these features are implemented, or to customize them:
• Controller: controllers/CountryController.php
• Models: models/Country.php and models/CountrySearch.php
• Views: views/country/*.php
Info: Gii is designed to be a highly customizable and extensible
code generation tool. Using it wisely can greatly accelerate your
application development speed. For more details, please refer to
the Gii section.
2.6.5 Summary
In this section, you have learned how to use Gii to generate the code that
implements complete CRUD functionality for content stored in a database
table.
2.7 Looking Ahead
If you’ve read through the entire “Getting Started” section, you have now
created a complete Yii application. In the process, you have learned how to
implement some commonly needed features, such as getting data from users
46 CHAPTER 2. GETTING STARTED
via an HTML form, fetching data from a database, and displaying data in
a paginated fashion. You have also learned how to use Gii to generate code
automatically. Using Gii for code generation turns the bulk of your Web
development process into a task as simple as just filling out some forms.
This section will summarize the Yii resources available to help you be
more productive when using the framework.
• Documentation
– The Definitive Guide: As the name indicates, the guide precisely
defines how Yii should work and provides general guidance about
using Yii. It is the single most important Yii tutorial, and one
that you should read before writing any Yii code.
– The Class Reference: This specifies the usage of every class provided
by Yii. It should be mainly used when you are writing code and
want to understand the usage of a particular class, method, prop-
erty. Usage of the class reference is best only after a contextual
understanding of the entire framework.
– The Wiki Articles: The wiki articles are written by Yii users based
on their own experiences. Most of them are written like cookbook
recipes, and show how to solve particular problems using Yii.
While the quality of these articles may not be as good as the
Definitive Guide, they are useful in that they cover broader topics
and can often provide ready-to-use solutions.
– Books
• Extensions18: Yii boasts a library of thousands of user-contributed
extensions that can be easily plugged into your applications, thereby
making your application development even faster and easier.
• Community
– Forum: http://www.yiiframework.com/forum/
– IRC chat: The #yii channel on the freenode network (irc://
irc.freenode.net/yii)
– GitHub: https://github.com/yiisoft/yii2
– Facebook: https://www.facebook.com/groups/yiitalk/
– Twitter: https://twitter.com/yiiframework
– LinkedIn: https://www.linkedin.com/groups/yii-framework-1483367
18
http://www.yiiframework.com/extensions/
Chapter 3
Application Structure
3.1 Overview
Yii applications are organized according to the model-view-controller (MVC)1
design pattern. Models represent data, business logic and rules; views are
output representation of models; and controllers take input and convert it
to commands for models and views.
Besides MVC, Yii applications also have the following entities:
• entry scripts: they are PHP scripts that are directly accessible by end
users. They are responsible for starting a request handling cycle.
• applications: they are globally accessible objects that manage applic-
ation components and coordinate them to fulfill requests.
• application components: they are objects registered with applications
and provide various services for fulfilling requests.
• modules: they are self-contained packages that contain complete MVC
by themselves. An application can be organized in terms of multiple
modules.
• filters: they represent code that need to be invoked before and after
the actual handling of each request by controllers.
• widgets: they are objects that can be embedded in views. They may
contain controller logic and can be reused in different views.
The following diagram shows the static structure of an application:
1
http://wikipedia.org/wiki/Model-view-controller
47
48 CHAPTER 3. APPLICATION STRUCTURE
3.2 Entry Scripts
Entry scripts are the first chain in the application bootstrapping process.
An application (either Web application or console application) has a single
entry script. End users make requests to entry scripts which instantiate
application instances and forward the requests to them.
Entry scripts for Web applications must be stored under Web accessible
directories so that they can be accessed by end users. They are often named
as index.php, but can also use any other names, provided Web servers can
locate them.
Entry scripts for console applications are usually stored under the base
path of applications and are named as yii (with the .php suffix). They should
be made executable so that users can run console applications through the
command ./yii <route> [arguments] [options].
Entry scripts mainly do the following work:
• Define global constants;
• Register Composer autoloader2;
• Include the Yii class file;
2
http://getcomposer.org/doc/01-basic-usage.md#autoloading
3.2. ENTRY SCRIPTS 49
• Load application configuration;
• Create and configure an application instance;
• Call yiibaseApplication::run() to process the incoming request.
3.2.1 Web Applications
The following is the code in the entry script for the Basic Web Application
Template.
<?php
defined(’YII_DEBUG’) or define(’YII_DEBUG’, true);
defined(’YII_ENV’) or define(’YII_ENV’, ’dev’);
// register Composer autoloader
require(__DIR__ . ’/../vendor/autoload.php’);
// include Yii class file
require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’);
// load application configuration
$config = require(__DIR__ . ’/../config/web.php’);
// create, configure and run application
(new yiiwebApplication($config))->run();
3.2.2 Console Applications
Similarly, the following is the code for the entry script of a console applica-
tion:
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
defined(’YII_DEBUG’) or define(’YII_DEBUG’, true);
// fcgi doesn’t have STDIN and STDOUT defined by default
defined(’STDIN’) or define(’STDIN’, fopen(’php://stdin’, ’r’));
defined(’STDOUT’) or define(’STDOUT’, fopen(’php://stdout’, ’w’));
// register Composer autoloader
require(__DIR__ . ’/vendor/autoload.php’);
// include Yii class file
50 CHAPTER 3. APPLICATION STRUCTURE
require(__DIR__ . ’/vendor/yiisoft/yii2/Yii.php’);
// load application configuration
$config = require(__DIR__ . ’/config/console.php’);
$application = new yiiconsoleApplication($config);
$exitCode = $application->run();
exit($exitCode);
3.2.3 Defining Constants
Entry scripts are the best place for defining global constants. Yii supports
the following three constants:
• YII_DEBUG: specifies whether the application is running in debug mode.
When in debug mode, an application will keep more log information,
and will reveal detailed error call stacks if exceptions are thrown. For
this reason, debug mode should be used mainly during development.
The default value of YII_DEBUG is false.
• YII_ENV: specifies which environment the application is running in. This
has been described in more detail in the Configurations section. The
default value of YII_ENV is ’prod’, meaning the application is running
in production environment.
• YII_ENABLE_ERROR_HANDLER: specifies whether to enable the error handler
provided by Yii. The default value of this constant is true.
When defining a constant, we often use the code like the following:
defined(’YII_DEBUG’) or define(’YII_DEBUG’, true);
which is equivalent to the following code:
if (!defined(’YII_DEBUG’)) {
define(’YII_DEBUG’, true);
}
Clearly the former is more succinct and easier to understand.
Constant definitions should be done at the very beginning of an entry
script so that they can take effect when other PHP files are being included.
3.3 Applications
Applications are objects that govern the overall structure and lifecycle of Yii
application systems. Each Yii application system contains a single applic-
ation object which is created in the entry script and is globally accessible
through the expression Yii::$app.
3.3. APPLICATIONS 51
Info: Depending on the context, when we say “an application”, it
can mean either an application object or an application system.
There are two types of applications: yiiwebApplication and yiiconsole
Application. As the names indicate, the former mainly handles Web re-
quests while the latter console command requests.
3.3.1 Application Configurations
When an entry script creates an application, it will load a configuration and
apply it to the application, like the following:
require(__DIR__ . ’/../vendor/autoload.php’);
require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’);
// load application configuration
$config = require(__DIR__ . ’/../config/web.php’);
// instantiate and configure the application
(new yiiwebApplication($config))->run();
Like normal configurations, application configurations specify how to ini-
tialize properties of application objects. Because application configurations
are often very complex, they usually are kept in configuration files, like the
web.php file in the above example.
3.3.2 Application Properties
There are many important application properties that you should configure
in application configurations. These properties typically describe the envir-
onment that applications are running in. For example, applications need to
know how to load controllers, where to store temporary files, etc. In the
following, we will summarize these properties.
Required Properties
In any application, you should at least configure two properties: yiibase
Application::id and yiibaseApplication::basePath.
yiibaseApplication::id The yiibaseApplication::id property
specifies a unique ID that differentiates an application from others. It is
mainly used programmatically. Although not a requirement, for best inter-
operability it is recommended that you use alphanumeric characters only
when specifying an application ID.
52 CHAPTER 3. APPLICATION STRUCTURE
yiibaseApplication::basePath The yiibaseApplication::basePath
property specifies the root directory of an application. It is the directory that
contains all protected source code of an application system. Under this dir-
ectory, you normally will see sub-directories such as models, views, controllers,
which contain source code corresponding to the MVC pattern.
You may configure the yiibaseApplication::basePath property us-
ing a directory path or a path alias. In both forms, the corresponding direct-
ory must exist, or an exception will be thrown. The path will be normalized
by calling the realpath() function.
The yiibaseApplication::basePath property is often used to derive
other important paths (e.g. the runtime path). For this reason, a path alias
named @app is predefined to represent this path. Derived paths may then be
formed using this alias (e.g. @app/runtime to refer to the runtime directory).
Important Properties
The properties described in this subsection often need to be configured be-
cause they differ across different applications.
yiibaseApplication::aliases This property allows you to define a
set of aliases in terms of an array. The array keys are alias names, and the
array values are the corresponding path definitions. For example,
[
’aliases’ => [
’@name1’ => ’path/to/path1’,
’@name2’ => ’path/to/path2’,
],
]
This property is provided such that you can define aliases in terms of applic-
ation configurations instead of the method calls Yii::setAlias().
yiibaseApplication::bootstrap This is a very useful property. It
allows you to specify an array of components that should be run during
the application yiibaseApplication::bootstrap(). For example, if you
want a module to customize the URL rules, you may list its ID as an element
in this property.
Each component listed in this property may be specified in one of the
following formats:
• an application component ID as specified via components.
• a module ID as specified via modules.
• a class name.
• a configuration array.
3.3. APPLICATIONS 53
• an anonymous function that creates and returns a component.
For example,
[
’bootstrap’ => [
// an application component ID or module ID
’demo’,
// a class name
’appcomponentsProfiler’,
// a configuration array
[
’class’ => ’appcomponentsProfiler’,
’level’ => 3,
],
// an anonymous function
function () {
return new appcomponentsProfiler();
}
],
]
Info: If a module ID is the same as an application component ID,
the application component will be used during the bootstrapping
process. If you want to use the module instead, you may specify
it using an anonymous function like the following: >‘php [
function () {
return Yii::$app->getModule(’user’);
},
] ‘
During the bootstrapping process, each component will be instantiated. If
the component class implements yiibaseBootstrapInterface, its yii
baseBootstrapInterface::bootstrap() method will also be called.
Another practical example is in the application configuration for the Ba-
sic Application Template, where the debug and gii modules are configured as
bootstrapping components when the application is running in development
environment,
if (YII_ENV_DEV) {
// configuration adjustments for ’dev’ environment
$config[’bootstrap’][] = ’debug’;
$config[’modules’][’debug’] = ’yiidebugModule’;
$config[’bootstrap’][] = ’gii’;
$config[’modules’][’gii’] = ’yiigiiModule’;
}
54 CHAPTER 3. APPLICATION STRUCTURE
Note: Putting too many components in bootstrap will degrade
the performance of your application because for each request, the
same set of components need to be run. So use bootstrapping
components judiciously.
yiiwebApplication::catchAll This property is supported by yiiweb
Application only. It specifies a controller action which should handle all
user requests. This is mainly used when the application is in maintenance
mode and needs to handle all incoming requests via a single action.
The configuration is an array whose first element specifies the route of
the action. The rest of the array elements (key-value pairs) specify the
parameters to be bound to the action. For example,
[
’catchAll’ => [
’offline/notice’,
’param1’ => ’value1’,
’param2’ => ’value2’,
],
]
yiibaseApplication::components This is the single most important
property. It allows you to register a list of named components called applic-
ation components that you can use in other places. For example,
[
’components’ => [
’cache’ => [
’class’ => ’yiicachingFileCache’,
],
’user’ => [
’identityClass’ => ’appmodelsUser’,
’enableAutoLogin’ => true,
],
],
]
Each application component is specified as a key-value pair in the array. The
key represents the component ID, while the value represents the component
class name or configuration.
You can register any component with an application, and the component
can later be accessed globally using the expression Yii::$app->ComponentID.
Please read the Application Components section for details.
yiibaseApplication::controllerMap This property allows you to map
a controller ID to an arbitrary controller class. By default, Yii maps control-
ler IDs to controller classes based on a convention (e.g. the ID post would be
mapped to appcontrollersPostController). By configuring this property, you
3.3. APPLICATIONS 55
can break the convention for specific controllers. In the following example,
account will be mapped to appcontrollersUserController, while article will
be mapped to appcontrollersPostController.
[
’controllerMap’ => [
[
’account’ => ’appcontrollersUserController’,
’article’ => [
’class’ => ’appcontrollersPostController’,
’enableCsrfValidation’ => false,
],
],
],
]
The array keys of this property represent the controller IDs, while the array
values represent the corresponding controller class names or configurations.
yiibaseApplication::controllerNamespace This property specifies
the default namespace under which controller classes should be located. It
defaults to appcontrollers. If a controller ID is post, by convention the cor-
responding controller class name (without namespace) would be PostController
, and the fully qualified class name would be appcontrollersPostController.
Controller classes may also be located under sub-directories of the dir-
ectory corresponding to this namespace. For example, given a controller ID
admin/post, the corresponding fully qualified controller class would be app
controllersadminPostController.
It is important that the fully qualified controller classes should be auto-
loadable and the actual namespace of your controller classes match the value
of this property. Otherwise, you will receive “Page Not Found” error when
accessing the application.
In case you want to break the convention as described above, you may
configure the controllerMap property.
yiibaseApplication::language This property specifies the language
in which the application should display content to end users. The default
value of this property is en, meaning English. You should configure this
property if your application needs to support multiple languages.
The value of this property determines various internationalization as-
pects, including message translation, date formatting, number formatting,
etc. For example, the yiijuiDatePicker widget will use this property
value by default to determine in which language the calendar should be dis-
played and how should the date be formatted.
It is recommended that you specify a language in terms of an IETF
language tag3. For example, en stands for English, while en-US stands for
3
http://en.wikipedia.org/wiki/IETF_language_tag
56 CHAPTER 3. APPLICATION STRUCTURE
English (United States).
More details about this property can be found in the Internationalization
section.
yiibaseApplication::modules This property specifies the modules
that the application contains.
The property takes an array of module classes or configurations with the
array keys being the module IDs. For example,
[
’modules’ => [
// a "booking" module specified with the module class
’booking’ => ’appmodulesbookingBookingModule’,
// a "comment" module specified with a configuration array
’comment’ => [
’class’ => ’appmodulescommentCommentModule’,
’db’ => ’db’,
],
],
]
Please refer to the Modules section for more details.
yiibaseApplication::name This property specifies the application name
that may be displayed to end users. Unlike the yiibaseApplication::
id property which should take a unique value, the value of this property is
mainly for display purpose and does not need to be unique.
You do not always need to configure this property if none of your code
is using it.
yiibaseApplication::params This property specifies an array of glob-
ally accessible application parameters. Instead of using hardcoded numbers
and strings everywhere in your code, it is a good practice to define them as
application parameters in a single place and use the parameters in places
where needed. For example, you may define the thumbnail image size as a
parameter like the following:
[
’params’ => [
’thumbnail.size’ => [128, 128],
],
]
Then in your code where you need to use the size value, you can simply use
the code like the following:
$size = Yii::$app->params[’thumbnail.size’];
$width = Yii::$app->params[’thumbnail.size’][0];
3.3. APPLICATIONS 57
Later if you decide to change the thumbnail size, you only need to modify it
in the application configuration without touching any dependent code.
yiibaseApplication::sourceLanguage This property specifies the lan-
guage that the application code is written in. The default value is ’en-US’,
meaning English (United States). You should configure this property if the
text content in your code is not in English.
Like the language property, you should configure this property in terms
of an IETF language tag4. For example, en stands for English, while en-US
stands for English (United States).
More details about this property can be found in the Internationalization
section.
yiibaseApplication::timeZone This property is provided as an al-
ternative way of setting the default time zone of PHP runtime. By configur-
ing this property, you are essentially calling the PHP function date_default_timezone_set()5.
For example,
[
’timeZone’ => ’America/Los_Angeles’,
]
yiibaseApplication::version This property specifies the version of
the application. It defaults to ’1.0’. You do not always need to configure
this property if none of your code is using it.
Useful Properties
The properties described in this subsection are not commonly configured
because their default values stipulate common conventions. However, you
may still configure them in case you want to break the conventions.
yiibaseApplication::charset This property specifies the charset that
the application uses. The default value is ’UTF-8’ which should be kept as is
for most applications unless you are working with some legacy systems that
use a lot of non-unicode data.
yiibaseApplication::defaultRoute This property specifies the route
that an application should use when a request does not specify one. The
route may consist of child module ID, controller ID, and/or action ID. For
example, help, post/create, admin/post/create. If action ID is not given, it will
take the default value as specified in yiibaseController::defaultAction.
4
http://en.wikipedia.org/wiki/IETF_language_tag
5
http://php.net/manual/en/function.date-default-timezone-set.php
58 CHAPTER 3. APPLICATION STRUCTURE
For yiiwebApplication, the default value of this property is ’site’,
which means the SiteController controller and its default action should be
used. As a result, if you access the application without specifying a route,
it will show the result of appcontrollersSiteController::actionIndex().
For yiiconsoleApplication, the default value is ’help’, which means
the core command yiiconsolecontrollersHelpController::actionIndex()
should be used. As a result, if you run the command yii without providing
any arguments, it will display the help information.
yiibaseApplication::extensions This property specifies the list of
extensions that are installed and used by the application. By default, it
will take the array returned by the file @vendor/yiisoft/extensions.php. The
extensions.php file is generated and maintained automatically when you use
Composer6 to install extensions. So in most cases, you do not need to con-
figure this property.
In the special case when you want to maintain extensions manually, you
may configure this property like the following:
[
’extensions’ => [
[
’name’ => ’extension name’,
’version’ => ’version number’,
’bootstrap’ => ’BootstrapClassName’, // optional, may also be a
configuration array
’alias’ => [ // optional
’@alias1’ => ’to/path1’,
’@alias2’ => ’to/path2’,
],
],
// ... more extensions like the above ...
],
]
As you can see, the property takes an array of extension specifications. Each
extension is specified with an array consisting of name and version elements. If
an extension needs to run during the bootstrap process, a bootstrap element
may be specified with a bootstrapping class name or a configuration array.
An extension may also define a few aliases.
yiibaseApplication::layout This property specifies the name of the
default layout that should be used when rendering a view. The default value
is ’main’, meaning the layout file main.php under the layout path should be
used. If both of the layout path and the view path are taking the default
6
http://getcomposer.org
3.3. APPLICATIONS 59
values, the default layout file can be represented as the path alias @app/views
/layouts/main.php.
You may configure this property to be false if you want to disable layout
by default, although this is very rare.
yiibaseApplication::layoutPath This property specifies the path where
layout files should be looked for. The default value is the layouts sub-
directory under the view path. If the view path is taking its default value, the
default layout path can be represented as the path alias @app/views/layouts.
You may configure it as a directory or a path alias.
yiibaseApplication::runtimePath This property specifies the path
where temporary files, such as log files, cache files, can be generated. The
default value is the directory represented by the alias @app/runtime.
You may configure it as a directory or a path alias. Note that the runtime
path must be writable by the process running the application. And the path
should be protected from being accessed by end users because the temporary
files under it may contain sensitive information.
To simplify accessing to this path, Yii has predefined a path alias named
@runtime for it.
yiibaseApplication::viewPath This property specifies the root dir-
ectory where view files are located. The default value is the directory rep-
resented by the alias @app/views. You may configure it as a directory or a
path alias.
yiibaseApplication::vendorPath This property specifies the vendor
directory managed by Composer7. It contains all third party libraries used
by your application, including the Yii framework. The default value is the
directory represented by the alias @app/vendor.
You may configure this property as a directory or a path alias. When you
modify this property, make sure you also adjust the Composer configuration
accordingly.
To simplify accessing to this path, Yii has predefined a path alias named
@vendor for it.
yiiconsoleApplication::enableCoreCommands This property is sup-
ported by yiiconsoleApplication only. It specifies whether the core
commands included in the Yii release should be enabled. The default value
is true.
7
http://getcomposer.org
60 CHAPTER 3. APPLICATION STRUCTURE
3.3.3 Application Events
An application triggers several events during the lifecycle of handling an
request. You may attach event handlers to these events in application con-
figurations like the following,
[
’on beforeRequest’ => function ($event) {
// ...
},
]
The use of the on eventName syntax is described in the Configurations section.
Alternatively, you may attach event handlers during the bootstrapping
process process after the application instance is created. For example,
Yii::$app->on(yiibaseApplication::EVENT_BEFORE_REQUEST, function ($event
) {
// ...
});
yiibaseApplication::EVENT_BEFORE_REQUEST
This event is triggered before an application handles a request. The actual
event name is beforeRequest.
When this event is triggered, the application instance has been configured
and initialized. So it is a good place to insert your custom code via the
event mechanism to intercept the request handling process. For example, in
the event handler, you may dynamically set the yiibaseApplication::
language property based on some parameters.
yiibaseApplication::EVENT_BEFORE_REQUEST
This event is triggered after an application finishes handling a request but
before sending the response. The actual event name is afterRequest.
When this event is triggered, the request handling is completed and you
may take this chance to do some postprocessing of the request or customize
the response.
Note that the yiiwebResponse component also triggers some events
while it is sending out response content to end users. Those events are
triggered after this event.
yiibaseApplication::EVENT_BEFORE_REQUEST
This event is triggered before running every controller action. The actual
event name is beforeAction.
The event parameter is an instance of yiibaseActionEvent. An event
handler may set the yiibaseActionEvent::isValid property to be false
to stop running the action. For example,
3.3. APPLICATIONS 61
[
’on beforeAction’ => function ($event) {
if (some condition) {
$event->isValid = false;
} else {
}
},
]
Note that the same beforeAction event is also triggered by modules and con-
trollers. Application objects are the first ones triggering this event, followed
by modules (if any), and finally controllers. If an event handler sets yiibase
ActionEvent::isValid to be false, all the following events will NOT be
triggered.
yiibaseApplication::EVENT_BEFORE_REQUEST
This event is triggered after running every controller action. The actual
event name is afterAction.
The event parameter is an instance of yiibaseActionEvent. Through
the yiibaseActionEvent::result property, an event handler may access
or modify the action result. For example,
[
’on afterAction’ => function ($event) {
if (some condition) {
// modify $event->result
} else {
}
},
]
Note that the same afterAction event is also triggered by modules and con-
trollers. These objects trigger this event in the reverse order as for that of
beforeAction. That is, controllers are the first objects triggering this event,
followed by modules (if any), and finally applications.
3.3.4 Application Lifecycle
When an entry script is being executed to handle a request, an application
will undergo the following lifecycle:
1. The entry script loads the application configuration as an array.
2. The entry script creates a new instance of the application:
• yiibaseApplication::preInit() is called, which configures
some high priority application properties, such as yiibaseApplication
::basePath.
• Register the yiibaseApplication::errorHandler.
62 CHAPTER 3. APPLICATION STRUCTURE
• Configure application properties.
• yiibaseApplication::init() is called which further calls yii
baseApplication::bootstrap() to run bootstrapping com-
ponents.
3. The entry script calls yiibaseApplication::run() to run the ap-
plication:
• Trigger the yiibaseApplication::EVENT_BEFORE_REQUEST event.
• Handle the request: resolve the request into a route and the associ-
ated parameters; create the module, controller and action objects
as specified by the route; and run the action.
• Trigger the yiibaseApplication::EVENT_AFTER_REQUEST event.
• Send response to the end user.
4. The entry script receives the exit status from the application and com-
pletes the request processing.
3.4 Application Components
Applications are service locators. They host a set of the so-called applica-
tion components that provide different services for processing requests. For
example, the urlManager component is responsible for routing Web requests
to appropriate controllers; the db component provides DB-related services;
and so on.
Each application component has an ID that uniquely identifies itself
among other application components in the same application. You can access
an application component through the expression
Yii::$app->componentID
For example, you can use Yii::$app->db to get the yiidbConnection, and
Yii::$app->cache to get the yiicachingCache registered with the applic-
ation.
An application component is created the first time it is accessed through
the above expression. Any further accesses will return the same component
instance.
Application components can be any objects. You can register them by
configuring the yiibaseApplication::components property in applica-
tion configurations. For example,
[
’components’ => [
// register "cache" component using a class name
’cache’ => ’yiicachingApcCache’,
3.4. APPLICATION COMPONENTS 63
// register "db" component using a configuration array
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
],
// register "search" component using an anonymous function
’search’ => function () {
return new appcomponentsSolrService;
},
],
]
Info: While you can register as many application components
as you want, you should do this judiciously. Application com-
ponents are like global variables. Using too many application
components can potentially make your code harder to test and
maintain. In many cases, you can simply create a local compon-
ent and use it when needed.
3.4.1 Bootstrapping Components
As mentioned above, an application component will only be instantiated
when it is being accessed the first time. If it is not accessed at all during a
request, it will not be instantiated. Sometimes, however, you may want to
instantiate an application component for every request, even if it is not expli-
citly accessed. To do so, you may list its ID in the yiibaseApplication
::bootstrap property of the application.
For example, the following application configuration makes sure the log
component is always loaded:
[
’bootstrap’ => [
’log’,
],
’components’ => [
’log’ => [
// configuration for "log" component
],
],
]
3.4.2 Core Application Components
Yii defines a set of core application components with fixed IDs and default
configurations. For example, the yiiwebApplication::request compon-
ent is used to collect information about a user request and resolve it into
64 CHAPTER 3. APPLICATION STRUCTURE
a route; the yiibaseApplication::db component represents a database
connection through which you can perform database queries. It is with help
of these core application components that Yii applications are able to handle
user requests.
Below is the list of the predefined core application components. You may
configure and customize them like you do with normal application compon-
ents. When you are configuring a core application component, if you do not
specify its class, the default one will be used.
• yiiwebAssetManager: manages asset bundles and asset publishing.
Please refer to the Managing Assets section for more details.
• yiidbConnection: represents a database connection through which
you can perform DB queries. Note that when you configure this com-
ponent, you must specify the component class as well as other required
component properties, such as yiidbConnection::dsn. Please refer
to the Data Access Objects section for more details.
• yiibaseApplication::errorHandler: handles PHP errors and ex-
ceptions. Please refer to the Handling Errors section for more details.
• yiii18nFormatter: formats data when they are displayed to end
users. For example, a number may be displayed with thousand separ-
ator, a date may be formatted in long format. Please refer to the Data
Formatting section for more details.
• yiii18nI18N: supports message translation and formatting. Please
refer to the Internationalization section for more details.
• yiilogDispatcher: manages log targets. Please refer to the Logging
section for more details.
• yiiswiftmailerMailer: supports mail composing and sending. Please
refer to the Mailing section for more details.
• yiibaseApplication::response: represents the response being sent
to end users. Please refer to the Responses section for more details.
• yiibaseApplication::request: represents the request received from
end users. Please refer to the Requests section for more details.
• yiiwebSession: represents the session information. This compon-
ent is only available in yiiwebApplication. Please refer to the
Sessions and Cookies section for more details.
• yiiwebUrlManager: supports URL parsing and creation. Please
refer to the URL Parsing and Generation section for more details.
3.5. CONTROLLERS 65
• yiiwebUser: represents the user authentication information. This
component is only available in yiiwebApplication Please refer to
the Authentication section for more details.
• yiiwebView: supports view rendering. Please refer to the Views
section for more details.
3.5 Controllers
Controllers are part of the MVC8 architecture. They are objects of classes
extending from yiibaseController and are responsible for processing
requests and generating responses. In particular, after taking over the control
from applications, controllers will analyze incoming request data, pass them
to models, inject model results into views, and finally generate outgoing
responses.
3.5.1 Actions
Controllers are composed by actions which are the most basic units that end
users can address and request for execution. A controller can have one or
multiple actions.
The following example shows a post controller with two actions: view and
create:
namespace appcontrollers;
use Yii;
use appmodelsPost;
use yiiwebController;
use yiiwebNotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model === null) {
throw new NotFoundHttpException;
}
return $this->render(’view’, [
’model’ => $model,
]);
}
public function actionCreate()
{
8
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
66 CHAPTER 3. APPLICATION STRUCTURE
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect([’view’, ’id’ => $model->id]);
} else {
return $this->render(’create’, [
’model’ => $model,
]);
}
}
}
In the view action (defined by the actionView() method), the code first loads
the model according to the requested model ID; If the model is loaded suc-
cessfully, it will display it using a view named view. Otherwise, it will throw
an exception.
In the create action (defined by the actionCreate() method), the code is
similar. It first tries to populate the model using the request data and save
the model. If both succeed it will redirect the browser to the view action
with the ID of the newly created model. Otherwise it will display the create
view through which users can provide the needed input.
3.5.2 Routes
End users address actions through the so-called routes. A route is a string
that consists of the following parts:
• a module ID: this exists only if the controller belongs to a non-application
module;
• a controller ID: a string that uniquely identifies the controller among
all controllers within the same application (or the same module if the
controller belongs to a module);
• an action ID: a string that uniquely identifies the action among all
actions within the same controller.
Routes take the following format:
ControllerID/ActionID
or the following format if the controller belongs to a module:
ModuleID/ControllerID/ActionID
So if a user requests with the URL http://hostname/index.php?r=site/index,
the index action in the site controller will be executed. For more details how
routes are resolved into actions, please refer to the Routing section.
3.5. CONTROLLERS 67
3.5.3 Creating Controllers
In yiiwebApplication, controllers should extend from yiiwebController
or its child classes. Similarly in yiiconsoleApplication, controllers
should extend from yiiconsoleController or its child classes. The fol-
lowing code defines a site controller:
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
}
Controller IDs
Usually, a controller is designed to handle the requests regarding a particular
type of resource. For this reason, controller IDs are often nouns referring to
the types of the resources that they are handling. For example, you may use
article as the ID of a controller that handles article data.
By default, controller IDs should contain these characters only: English
letters in lower case, digits, underscores, dashes and forward slashes. For
example, article and post-comment are both valid controller IDs, while article
?, PostComment, adminpost are not.
A controller ID may also contain a subdirectory prefix. For example,
admin/article stands for an article controller in the admin subdirectory under
the yiibaseApplication::controllerNamespace. Valid characters for
subdirectory prefixes include: English letters in lower and upper cases, digits,
underscores and forward slashes, where forward slashes are used as separators
for multi-level subdirectories (e.g. panels/admin).
Controller Class Naming
Controller class names can be derived from controller IDs according to the
following rules:
• Turn the first letter in each word separated by dashes into upper case.
Note that if the controller ID contains slashes, this rule only applies to
the part after the last slash in the ID.
• Remove dashes and replace any forward slashes with backward slashes.
• Append the suffix Controller.
• And prepend the yiibaseApplication::controllerNamespace.
68 CHAPTER 3. APPLICATION STRUCTURE
The followings are some examples, assuming the yiibaseApplication::
controllerNamespace takes the default value appcontrollers:
• article derives appcontrollersArticleController;
• post-comment derives appcontrollersPostCommentController;
• admin/post-comment derives appcontrollersadminPostCommentController;
• adminPanels/post-comment derives appcontrollersadminPanelsPostCommentController
.
Controller classes must be autoloadable. For this reason, in the above ex-
amples, the article controller class should be saved in the file whose alias
is @app/controllers/ArticleController.php; while the admin/post2-comment con-
troller should be in @app/controllers/admin/Post2CommentController.php.
Info: The last example admin/post2-comment shows how you can
put a controller under a sub-directory of the yiibaseApplication
::controllerNamespace. This is useful when you want to organ-
ize your controllers into several categories and you do not want
to use modules.
Controller Map
You can configure yiibaseApplication::controllerMap to overcome the
constraints of the controller IDs and class names described above. This is
mainly useful when you are using some third-party controllers which you do
not control over their class names.
You may configure yiibaseApplication::controllerMap in the ap-
plication configuration like the following:
[
’controllerMap’ => [
// declares "account" controller using a class name
’account’ => ’appcontrollersUserController’,
// declares "article" controller using a configuration array
’article’ => [
’class’ => ’appcontrollersPostController’,
’enableCsrfValidation’ => false,
],
],
]
3.5. CONTROLLERS 69
Default Controller
Each application has a default controller specified via the yiibaseApplication
::defaultRoute property. When a request does not specify a route, the
route specified by this property will be used. For yiiwebApplication, its
value is ’site’, while for yiiconsoleApplication, it is help. Therefore, if
a URL is http://hostname/index.php, it means the site controller will handle
the request.
You may change the default controller with the following application
configuration:
[
’defaultRoute’ => ’main’,
]
3.5.4 Creating Actions
Creating actions can be as simple as defining the so-called action methods in
a controller class. An action method is a public method whose name starts
with the word action. The return value of an action method represents the
response data to be sent to end users. The following code defines two actions
index and hello-world:
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
public function actionIndex()
{
return $this->render(’index’);
}
public function actionHelloWorld()
{
return ’Hello World’;
}
}
Action IDs
An action is often designed to perform a particular manipulation about a
resource. For this reason, action IDs are usually verbs, such as view, update,
etc.
By default, action IDs should contain these characters only: English let-
ters in lower case, digits, underscores and dashes. The dashes in an actionID
are used to separate words. For example, view, update2, comment-post are all
valid action IDs, while view?, Update are not.
70 CHAPTER 3. APPLICATION STRUCTURE
You can create actions in two ways: inline actions and standalone ac-
tions. An inline action is defined as a method in the controller class, while
a standalone action is a class extending yiibaseAction or its child class.
Inline actions take less effort to create and are often preferred if you have
no intention to reuse these actions. Standalone actions, on the other hand,
are mainly created to be used in different controllers or be redistributed as
extensions.
Inline Actions
Inline actions refer to the actions that are defined in terms of action methods
as we just described.
The names of the action methods are derived from action IDs according
to the following criteria:
• Turn the first letter in each word of the action ID into upper case;
• Remove dashes;
• Prepend the prefix action.
For example, index becomes actionIndex, and hello-world becomes actionHelloWorld
.
Note: The names of the action methods are case-sensitive. If you
have a method named ActionIndex, it will not be considered as an
action method, and as a result, the request for the index action
will result in an exception. Also note that action methods must
be public. A private or protected method does NOT define an
inline action.
Inline actions are the most commonly defined actions because they take little
effort to create. However, if you plan to reuse the same action in different
places, or if you want to redistribute an action, you should consider defining
it as a standalone action.
Standalone Actions
Standalone actions are defined in terms of action classes extending yiibase
Action or its child classes. For example, in the Yii releases, there are yii
webViewAction and yiiwebErrorAction, both of which are standalone
actions.
To use a standalone action, you should declare it in the action map by
overriding the yiibaseController::actions() method in your controller
classes like the following:
3.5. CONTROLLERS 71
public function actions()
{
return [
// declares "error" action using a class name
’error’ => ’yiiwebErrorAction’,
// declares "view" action using a configuration array
’view’ => [
’class’ => ’yiiwebViewAction’,
’viewPrefix’ => ’’,
],
];
}
As you can see, the actions() method should return an array whose keys are
action IDs and values the corresponding action class names or configurations.
Unlike inline actions, action IDs for standalone actions can contain arbitrary
characters, as long as they are declared in the actions() method.
To create a standalone action class, you should extend yiibaseAction
or its child class, and implement a public method named run(). The role of
the run() method is similar to that of an action method. For example,
<?php
namespace appcomponents;
use yiibaseAction;
class HelloWorldAction extends Action
{
public function run()
{
return "Hello World";
}
}
Action Results
The return value of an action method or the run() method of a standalone
action is significant. It stands for the result of the corresponding action.
The return value can be a response object which will be sent to as the
response to end users.
• For yiiwebApplication, the return value can also be some arbit-
rary data which will be assigned to yiiwebResponse::data and be
further converted into a string representing the response body.
• For yiiconsoleApplication, the return value can also be an integer
representing the yiiconsoleResponse::exitStatus of the command
execution.
72 CHAPTER 3. APPLICATION STRUCTURE
In the examples shown above, the action results are all strings which will be
treated as the response body to be sent to end users. The following example
shows how an action can redirect the user browser to a new URL by returning
a response object (because the yiiwebController::redirect() method
returns a response object):
public function actionForward()
{
// redirect the user browser to http://example.com
return $this->redirect(’http://example.com’);
}
Action Parameters
The action methods for inline actions and the run() methods for standalone
actions can take parameters, called action parameters. Their values are ob-
tained from requests. For yiiwebApplication, the value of each action
parameter is retrieved from $_GET using the parameter name as the key;
for yiiconsoleApplication, they correspond to the command line argu-
ments.
In the following example, the view action (an inline action) has declared
two parameters: $id and $version.
namespace appcontrollers;
use yiiwebController;
class PostController extends Controller
{
public function actionView($id, $version = null)
{
// ...
}
}
The action parameters will be populated as follows for different requests:
• http://hostname/index.php?r=post/view&id=123: the $id parameter will be
filled with the value ’123’, while $version is still null because there is
no version query parameter.
• http://hostname/index.php?r=post/view&id=123&version=2: the $id and $version
parameters will be filled with ’123’ and ’2’, respectively.
• http://hostname/index.php?r=post/view: a yiiwebBadRequestHttpException
exception will be thrown because the required $id parameter is not
provided in the request.
• http://hostname/index.php?r=post/view&id[]=123: a yiiwebBadRequestHttpException
exception will be thrown because $id parameter is receiving an unex-
pected array value [’123’].
3.5. CONTROLLERS 73
If you want an action parameter to accept array values, you should type-hint
it with array, like the following:
public function actionView(array $id, $version = null)
{
// ...
}
Now if the request is http://hostname/index.php?r=post/view&id[]=123, the $id
parameter will take the value of [’123’]. If the request is http://hostname
/index.php?r=post/view&id=123, the $id parameter will still receive the same
array value because the scalar value ’123’ will be automatically turned into
an array.
The above examples mainly show how action parameters work for Web
applications. For console applications, please refer to the Console Commands
section for more details.
Default Action
Each controller has a default action specified via the yiibaseController
::defaultAction property. When a route contains the controller ID only,
it implies that the default action of the specified controller is requested.
By default, the default action is set as index. If you want to change the
default value, simply override this property in the controller class, like the
following:
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
public $defaultAction = ’home’;
public function actionHome()
{
return $this->render(’home’);
}
}
3.5.5 Controller Lifecycle
When processing a request, an application will create a controller based on
the requested route. The controller will then undergo the following lifecycle
to fulfill the request:
1. The yiibaseController::init() method is called after the con-
troller is created and configured.
74 CHAPTER 3. APPLICATION STRUCTURE
2. The controller creates an action object based on the requested action
ID:
• If the action ID is not specified, the yiibaseController::
defaultAction will be used.
• If the action ID is found in the yiibaseController::actions(),
a standalone action will be created;
• If the action ID is found to match an action method, an inline
action will be created;
• Otherwise an yiibaseInvalidRouteException exception will
be thrown.
3. The controller sequentially calls the beforeAction() method of the ap-
plication, the module (if the controller belongs to a module) and the
controller.
• If one of the calls returns false, the rest of the uncalled beforeAction
() will be skipped and the action execution will be cancelled.
• By default, each beforeAction() method call will trigger a beforeAction
event to which you can attach a handler.
4. The controller runs the action:
• The action parameters will be analyzed and populated from the
request data;
5. The controller sequentially calls the afterAction() method of the con-
troller, the module (if the controller belongs to a module) and the
application.
• By default, each afterAction() method call will trigger an afterAction
event to which you can attach a handler.
6. The application will take the action result and assign it to the response.
3.5.6 Best Practices
In a well-designed application, controllers are often very thin with each action
containing only a few lines of code. If your controller is rather complicated,
it usually indicates that you should refactor it and move some code to other
classes.
In summary, controllers
• may access the request data;
• may call methods of models and other service components with request
data;
3.6. MODELS 75
• may use views to compose responses;
• should NOT process the request data - this should be done in models;
• should avoid embedding HTML or other presentational code - this is
better done in views.
3.6 Models
Models are part of the MVC9 architecture. They are objects representing
business data, rules and logic.
You can create model classes by extending yiibaseModel or its child
classes. The base class yiibaseModel supports many useful features:
• Attributes: represent the business data and can be accessed like normal
object properties or array elements;
• Attribute labels: specify the display labels for attributes;
• Massive assignment: supports populating multiple attributes in a single
step;
• Validation rules: ensures input data based on the declared validation
rules;
• Data Exporting: allows model data to be exported in terms of arrays
with customizable formats.
The Model class is also the base class for more advanced models, such as
Active Record. Please refer to the relevant documentation for more details
about these advanced models.
Info: You are not required to base your model classes on yii
baseModel. However, because there are many Yii components
built to support yiibaseModel, it is usually the preferable base
class for a model.
3.6.1 Attributes
Models represent business data in terms of attributes. Each attribute is like
a publicly accessible property of a model. The method yiibaseModel::
attributes() specifies what attributes a model class has.
You can access an attribute like accessing a normal object property:
9
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
76 CHAPTER 3. APPLICATION STRUCTURE
$model = new appmodelsContactForm;
// "name" is an attribute of ContactForm
$model->name = ’example’;
echo $model->name;
You can also access attributes like accessing array elements, thanks to the
support for ArrayAccess10 and ArrayIterator11 by yiibaseModel:
$model = new appmodelsContactForm;
// accessing attributes like array elements
$model[’name’] = ’example’;
echo $model[’name’];
// iterate attributes
foreach ($model as $name => $value) {
echo "$name: $valuen";
}
Defining Attributes
By default, if your model class extends directly from yiibaseModel, all
its non-static public member variables are attributes. For example, the
ContactForm model class below has four attributes: name, email, subject and
body. The ContactForm model is used to represent the input data received
from an HTML form.
namespace appmodels;
use yiibaseModel;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
You may override yiibaseModel::attributes() to define attributes in a
different way. The method should return the names of the attributes in a
model. For example, yiidbActiveRecord does so by returning the column
names of the associated database table as its attribute names. Note that you
may also need to override the magic methods such as __get(), __set() so that
the attributes can be accessed like normal object properties.
10
http://php.net/manual/en/class.arrayaccess.php
11
http://php.net/manual/en/class.arrayiterator.php
3.6. MODELS 77
Attribute Labels
When displaying values or getting input for attributes, you often need to dis-
play some labels associated with attributes. For example, given an attribute
named firstName, you may want to display a label First Name which is more
user-friendly when displayed to end users in places such as form inputs and
error messages.
You can get the label of an attribute by calling yiibaseModel::getAttributeLabel().
For example,
$model = new appmodelsContactForm;
// displays "Name"
echo $model->getAttributeLabel(’name’);
By default, attribute labels are automatically generated from attribute names.
The generation is done by the method yiibaseModel::generateAttributeLabel().
It will turn camel-case variable names into multiple words with the first let-
ter in each word in upper case. For example, username becomes Username, and
firstName becomes First Name.
If you do not want to use automatically generated labels, you may over-
ride yiibaseModel::attributeLabels() to explicitly declare attribute
labels. For example,
namespace appmodels;
use yiibaseModel;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
public function attributeLabels()
{
return [
’name’ => ’Your name’,
’email’ => ’Your email address’,
’subject’ => ’Subject’,
’body’ => ’Content’,
];
}
}
For applications supporting multiple languages, you may want to translate
attribute labels. This can be done in the yiibaseModel::attributeLabels()
method as well, like the following:
public function attributeLabels()
{
78 CHAPTER 3. APPLICATION STRUCTURE
return [
’name’ => Yii::t(’app’, ’Your name’),
’email’ => Yii::t(’app’, ’Your email address’),
’subject’ => Yii::t(’app’, ’Subject’),
’body’ => Yii::t(’app’, ’Content’),
];
}
You may even conditionally define attribute labels. For example, based on
the scenario the model is being used in, you may return different labels for
the same attribute.
Info: Strictly speaking, attribute labels are part of views. But
declaring labels in models is often very convenient and can result
in very clean and reusable code.
3.6.2 Scenarios
A model may be used in different scenarios. For example, a User model
may be used to collect user login inputs, but it may also be used for the
user registration purpose. In different scenarios, a model may use different
business rules and logic. For example, the email attribute may be required
during user registration, but not so during user login.
A model uses the yiibaseModel::scenario property to keep track of
the scenario it is being used in. By default, a model supports only a single
scenario named default. The following code shows two ways of setting the
scenario of a model:
// scenario is set as a property
$model = new User;
$model->scenario = ’login’;
// scenario is set through configuration
$model = new User([’scenario’ => ’login’]);
By default, the scenarios supported by a model are determined by the valid-
ation rules declared in the model. However, you can customize this behavior
by overriding the yiibaseModel::scenarios() method, like the follow-
ing:
namespace appmodels;
use yiidbActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
return [
’login’ => [’username’, ’password’],
’register’ => [’username’, ’email’, ’password’],
3.6. MODELS 79
];
}
}
Info: In the above and following examples, the model classes
are extending from yiidbActiveRecord because the usage of
multiple scenarios usually happens to Active Record classes.
The scenarios() method returns an array whose keys are the scenario names
and values the corresponding active attributes. An active attribute can be
massively assigned and is subject to validation. In the above example, the
username and password attributes are active in the login scenario; while in the
register scenario, email is also active besides username and password.
The default implementation of scenarios() will return all scenarios found
in the validation rule declaration method yiibaseModel::rules(). When
overriding scenarios(), if you want to introduce new scenarios in addition to
the default ones, you may write code like the following:
namespace appmodels;
use yiidbActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[’login’] = [’username’, ’password’];
$scenarios[’register’] = [’username’, ’email’, ’password’];
return $scenarios;
}
}
The scenario feature is primarily used by validation and massive attribute
assignment. You can, however, use it for other purposes. For example, you
may declare attribute labels differently based on the current scenario.
3.6.3 Validation Rules
When the data for a model is received from end users, it should be validated
to make sure it satisfies certain rules (called validation rules, also known
as business rules). For example, given a ContactForm model, you may want
to make sure all attributes are not empty and the email attribute contains
a valid email address. If the values for some attributes do not satisfy the
corresponding business rules, appropriate error messages should be displayed
to help the user to fix the errors.
You may call yiibaseModel::validate() to validate the received
data. The method will use the validation rules declared in yiibaseModel
80 CHAPTER 3. APPLICATION STRUCTURE
::rules() to validate every relevant attribute. If no error is found, it will
return true. Otherwise, it will keep the errors in the yiibaseModel::
errors property and return false. For example,
$model = new appmodelsContactForm;
// populate model attributes with user inputs
$model->attributes = Yii::$app->request->post(’ContactForm’);
if ($model->validate()) {
// all inputs are valid
} else {
// validation failed: $errors is an array containing error messages
$errors = $model->errors;
}
To declare validation rules associated with a model, override the yiibase
Model::rules() method by returning the rules that the model attributes
should satisfy. The following example shows the validation rules declared for
the ContactForm model:
public function rules()
{
return [
// the name, email, subject and body attributes are required
[[’name’, ’email’, ’subject’, ’body’], ’required’],
// the email attribute should be a valid email address
[’email’, ’email’],
];
}
A rule can be used to validate one or multiple attributes, and an attribute
may be validated by one or multiple rules. Please refer to the Validating
Input section for more details on how to declare validation rules.
Sometimes, you may want a rule to be applied only in certain scenarios.
To do so, you can specify the on property of a rule, like the following:
public function rules()
{
return [
// username, email and password are all required in "register"
scenario
[[’username’, ’email’, ’password’], ’required’, ’on’ => ’register’],
// username and password are required in "login" scenario
[[’username’, ’password’], ’required’, ’on’ => ’login’],
];
}
If you do not specify the on property, the rule would be applied in all scen-
arios. A rule is called an active rule if it can be applied in the current yii
baseModel::scenario.
3.6. MODELS 81
An attribute will be validated if and only if it is an active attribute
declared in scenarios() and is associated with one or multiple active rules
declared in rules().
3.6.4 Massive Assignment
Massive assignment is a convenient way of populating a model with user
inputs using a single line of code. It populates the attributes of a model
by assigning the input data directly to the yiibaseModel::$attributes
property. The following two pieces of code are equivalent, both trying to as-
sign the form data submitted by end users to the attributes of the ContactForm
model. Clearly, the former, which uses massive assignment, is much cleaner
and less error prone than the latter:
$model = new appmodelsContactForm;
$model->attributes = Yii::$app->request->post(’ContactForm’);
$model = new appmodelsContactForm;
$data = Yii::$app->request->post(’ContactForm’, []);
$model->name = isset($data[’name’]) ? $data[’name’] : null;
$model->email = isset($data[’email’]) ? $data[’email’] : null;
$model->subject = isset($data[’subject’]) ? $data[’subject’] : null;
$model->body = isset($data[’body’]) ? $data[’body’] : null;
Safe Attributes
Massive assignment only applies to the so-called safe attributes which are
the attributes listed in yiibaseModel::scenarios() for the current yii
baseModel::scenario of a model. For example, if the User model has the
following scenario declaration, then when the current scenario is login, only
the username and password can be massively assigned. Any other attributes
will be kept untouched.
public function scenarios()
{
return [
’login’ => [’username’, ’password’],
’register’ => [’username’, ’email’, ’password’],
];
}
Info: The reason that massive assignment only applies to safe
attributes is because you want to control which attributes can be
modified by end user data. For example, if the User model has
a permission attribute which determines the permission assigned
to the user, you would like this attribute to be modifiable by
administrators through a backend interface only.
82 CHAPTER 3. APPLICATION STRUCTURE
Because the default implementation of yiibaseModel::scenarios() will
return all scenarios and attributes found in yiibaseModel::rules(), if
you do not override this method, it means an attribute is safe as long as it
appears in one of the active validation rules.
For this reason, a special validator aliased safe is provided so that you can
declare an attribute to be safe without actually validating it. For example,
the following rules declare that both title and description are safe attributes.
public function rules()
{
return [
[[’title’, ’description’], ’safe’],
];
}
Unsafe Attributes
As described above, the yiibaseModel::scenarios() method serves for
two purposes: determining which attributes should be validated, and de-
termining which attributes are safe. In some rare cases, you may want to
validate an attribute but do not want to mark it safe. You can do so by
prefixing an exclamation mark ! to the attribute name when declaring it in
scenarios(), like the secret attribute in the following:
public function scenarios()
{
return [
’login’ => [’username’, ’password’, ’!secret’],
];
}
When the model is in the login scenario, all three attributes will be validated.
However, only the username and password attributes can be massively assigned.
To assign an input value to the secret attribute, you have to do it explicitly
as follows,
$model->secret = $secret;
3.6.5 Data Exporting
Models often need to be exported in different formats. For example, you
may want to convert a collection of models into JSON or Excel format. The
exporting process can be broken down into two independent steps. In the
first step, models are converted into arrays; in the second step, the arrays are
converted into target formats. You may just focus on the first step, because
the second step can be achieved by generic data formatters, such as yiiweb
JsonResponseFormatter.
The simplest way of converting a model into an array is to use the yii
baseModel::$attributes property. For example,
3.6. MODELS 83
$post = appmodelsPost::findOne(100);
$array = $post->attributes;
By default, the yiibaseModel::$attributes property will return the val-
ues of all attributes declared in yiibaseModel::attributes().
A more flexible and powerful way of converting a model into an array is
to use the yiibaseModel::toArray() method. Its default behavior is the
same as that of yiibaseModel::$attributes. However, it allows you to
choose which data items, called fields, to be put in the resulting array and
how they should be formatted. In fact, it is the default way of exporting
models in RESTful Web service development, as described in the Response
Formatting.
Fields
A field is simply a named element in the array that is obtained by calling
the yiibaseModel::toArray() method of a model.
By default, field names are equivalent to attribute names. However, you
can change this behavior by overriding the yiibaseModel::fields() an-
d/or yiibaseModel::extraFields() methods. Both methods should re-
turn a list of field definitions. The fields defined by fields() are default fields,
meaning that toArray() will return these fields by default. The extraFields()
method defines additionally available fields which can also be returned by
toArray() as long as you specify them via the $expand parameter. For ex-
ample, the following code will return all fields defined in fields() and the
prettyName and fullAddress fields if they are defined in extraFields().
$array = $model->toArray([], [’prettyName’, ’fullAddress’]);
You can override fields() to add, remove, rename or redefine fields. The
return value of fields() should be an array. The array keys are the field
names, and the array values are the corresponding field definitions which
can be either property/attribute names or anonymous functions returning
the corresponding field values. In the special case when a field name is the
same as its defining attribute name, you can omit the array key. For example,
// explicitly list every field, best used when you want to make sure the
changes
// in your DB table or model attributes do not cause your field changes (to
keep API backward compatibility).
public function fields()
{
return [
// field name is the same as the attribute name
’id’,
// field name is "email", the corresponding attribute name is "
email_address"
’email’ => ’email_address’,
84 CHAPTER 3. APPLICATION STRUCTURE
// field name is "name", its value is defined by a PHP callback
’name’ => function () {
return $this->first_name . ’ ’ . $this->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent
implementation
// and blacklist some sensitive fields.
public function fields()
{
$fields = parent::fields();
// remove fields that contain sensitive information
unset($fields[’auth_key’], $fields[’password_hash’], $fields[’
password_reset_token’]);
return $fields;
}
Warning: Because by default all attributes of a model will be
included in the exported array, you should examine your data
to make sure they do not contain sensitive information. If there
is such information, you should override fields() to filter them
out. In the above example, we choose to filter out auth_key,
password_hash and password_reset_token.
3.6.6 Best Practices
Models are the central places to represent business data, rules and logic.
They often need to be reused in different places. In a well-designed applica-
tion, models are usually much fatter than controllers.
In summary, models
• may contain attributes to represent business data;
• may contain validation rules to ensure the data validity and integrity;
• may contain methods implementing business logic;
• should NOT directly access request, session, or any other environ-
mental data. These data should be injected by controllers into models;
• should avoid embedding HTML or other presentational code - this is
better done in views;
• avoid having too many scenarios in a single model.
3.7. VIEWS 85
You may usually consider the last recommendation above when you are de-
veloping large complex systems. In these systems, models could be very fat
because they are used in many places and may thus contain many sets of
rules and business logic. This often ends up in a nightmare in maintaining
the model code because a single touch of the code could affect several dif-
ferent places. To make the mode code more maintainable, you may take the
following strategy:
• Define a set of base model classes that are shared by different applica-
tions or modules. These model classes should contain minimal sets of
rules and logic that are common among all their usages.
• In each application or module that uses a model, define a concrete
model class by extending from the corresponding base model class.
The concrete model classes should contain rules and logic that are
specific for that application or module.
For example, in the Advanced Application Template, you may define a base
model class commonmodelsPost. Then for the front end application, you
define and use a concrete model class frontendmodelsPost which extends
from commonmodelsPost. And similarly for the back end application, you
define backendmodelsPost. With this strategy, you will be sure that the code
in frontendmodelsPost is only specific to the front end application, and if
you make any change to it, you do not need to worry if the change may
break the back end application.
3.7 Views
Views are part of the MVC12 architecture. They are code responsible for
presenting data to end users. In a Web application, views are usually created
in terms of view templates which are PHP script files containing mainly
HTML code and presentational PHP code. They are managed by the yii
webView application component which provides commonly used methods
to facilitate view composition and rendering. For simplicity, we often call
view templates or view template files as views.
3.7.1 Creating Views
As aforementioned, a view is simply a PHP script mixed with HTML and
PHP code. The following is the view that presents a login form. As you can
see, PHP code is used to generate the dynamic content, such as the page title
and the form, while HTML code organizes them into a presentable HTML
page.
12
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
86 CHAPTER 3. APPLICATION STRUCTURE
<?php
use yiihelpersHtml;
use yiiwidgetsActiveForm;
/* @var $this yiiwebView */
/* @var $form yiiwidgetsActiveForm */
/* @var $model appmodelsLoginForm */
$this->title = ’Login’;
?>
<h1><?= Html::encode($this->title) ?></h1>
<p>Please fill out the following fields to login:</p>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, ’username’) ?>
<?= $form->field($model, ’password’)->passwordInput() ?>
<?= Html::submitButton(’Login’) ?>
<?php ActiveForm::end(); ?>
Within a view, you can access $this which refers to the yiiwebView man-
aging and rendering this view template.
Besides $this, there may be other predefined variables in a view, such
as $model in the above example. These variables represent the data that are
pushed into the view by controllers or other objects whose trigger the view
rendering.
Tip: The predefined variables are listed in a comment block at
beginning of a view so that they can be recognized by IDEs. It
is also a good way of documenting your views.
Security
When creating views that generate HTML pages, it is important that you
encode and/or filter the data coming from end users before presenting them.
Otherwise, your application may be subject to cross-site scripting13 attacks.
To display a plain text, encode it first by calling yiihelpersHtml::
encode(). For example, the following code encodes the user name before
displaying it:
<?php
use yiihelpersHtml;
?>
<div class="username">
<?= Html::encode($user->name) ?>
</div>
13
http://en.wikipedia.org/wiki/Cross-site_scripting
3.7. VIEWS 87
To display HTML content, use yiihelpersHtmlPurifier to filter the con-
tent first. For example, the following code filters the post content before
displaying it:
<?php
use yiihelpersHtmlPurifier;
?>
<div class="post">
<?= HtmlPurifier::process($post->text) ?>
</div>
Tip: While HTMLPurifier does excellent job in making output
safe, it is not fast. You should consider caching the filtering result
if your application requires high performance.
Organizing Views
Like controllers and models, there are conventions to organize views.
• For views rendered by a controller, they should be put under the dir-
ectory @app/views/ControllerID by default, where ControllerID refers to
the controller ID. For example, if the controller class is PostController,
the directory would be @app/views/post; If it is PostCommentController,
the directory would be @app/views/post-comment. In case the controller
belongs to a module, the directory would be views/ControllerID under
the yiibaseModule::basePath.
• For views rendered in a widget, they should be put under the WidgetPath
/views directory by default, where WidgetPath stands for the directory
containing the widget class file.
• For views rendered by other objects, it is recommended that you follow
the similar convention as that for widgets.
You may customize these default view directories by overriding the yiibase
ViewContextInterface::getViewPath() method of controllers or widgets.
3.7.2 Rendering Views
You can render views in controllers, widgets, or any other places by calling
view rendering methods. These methods share a similar signature shown as
follows,
/**
* @param string $view view name or file path, depending on the actual
rendering method
* @param array $params the data to be passed to the view
* @return string rendering result
88 CHAPTER 3. APPLICATION STRUCTURE
*/
methodName($view, $params = [])
Rendering in Controllers
Within controllers, you may call the following controller methods to render
views:
• yiibaseController::render(): renders a named view and applies
a layout to the rendering result.
• yiibaseController::renderPartial(): renders a named view without
any layout.
• yiiwebController::renderAjax(): renders a named view without
any layout, and injects all registered JS/CSS scripts and files. It is
usually used in response to AJAX Web requests.
• yiibaseController::renderFile(): renders a view specified in
terms of a view file path or alias.
For example,
namespace appcontrollers;
use Yii;
use appmodelsPost;
use yiiwebController;
use yiiwebNotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model === null) {
throw new NotFoundHttpException;
}
// renders a view named "view" and applies a layout to it
return $this->render(’view’, [
’model’ => $model,
]);
}
}
Rendering in Widgets
Within widgets, you may call the following widget methods to render views.
3.7. VIEWS 89
• yiibaseWidget::render(): renders a named view.
• yiibaseWidget::renderFile(): renders a view specified in terms
of a view file path or alias.
For example,
namespace appcomponents;
use yiibaseWidget;
use yiihelpersHtml;
class ListWidget extends Widget
{
public $items = [];
public function run()
{
// renders a view named "list"
return $this->render(’list’, [
’items’ => $this->items,
]);
}
}
Rendering in Views
You can render a view within another view by calling one of the following
methods provided by the yiibaseView:
• yiibaseView::render(): renders a named view.
• yiiwebView::renderAjax(): renders a named view and injects all
registered JS/CSS scripts and files. It is usually used in response to
AJAX Web requests.
• yiibaseView::renderFile(): renders a view specified in terms of
a view file path or alias.
For example, the following code in a view renders the _overview.php view
file which is in the same directory as the view being currently rendered.
Remember that $this in a view refers to the yiibaseView component:
<?= $this->render(’_overview’) ?>
Rendering in Other Places
In any place, you can get access to the yiibaseView application com-
ponent by the expression Yii::$app->view and then call its aforementioned
methods to render a view. For example,
90 CHAPTER 3. APPLICATION STRUCTURE
// displays the view file "@app/views/site/license.php"
echo Yii::$app->view->renderFile(’@app/views/site/license.php’);
Named Views
When you render a view, you can specify the view using either a view name
or a view file path/alias. In most cases, you would use the former because it
is more concise and flexible. We call views specified using names as named
views.
A view name is resolved into the corresponding view file path according
to the following rules:
• A view name may omit the file extension name. In this case, .php will be
used as the extension. For example, the view name about corresponds
to the file name about.php.
• If the view name starts with double slashes //, the corresponding view
file path would be @app/views/ViewName. That is, the view is looked for
under the yiibaseApplication::viewPath. For example, //site/
about will be resolved into @app/views/site/about.php.
• If the view name starts with a single slash /, the view file path is formed
by prefixing the view name with the yiibaseModule::viewPath of
the currently active module. If there is no active module, @app/views/
ViewName will be used. For example, /user/create will be resolved into
@app/modules/user/views/user/create.php, if the currently active module
is user. If there is no active module, the view file path would be @app/
views/user/create.php.
• If the view is rendered with a yiibaseView::context and the con-
text implements yiibaseViewContextInterface, the view file path
is formed by prefixing the yiibaseViewContextInterface::getViewPath()
of the context to the view name. This mainly applies to the views
rendered within controllers and widgets. For example, site/about will
be resolved into @app/views/site/about.php if the context is the control-
ler SiteController.
• If a view is rendered within another view, the directory containing the
other view file will be prefixed to the new view name to form the actual
view file path. For example, item will be resolved into @app/views/post
/item if it is being rendered in the view @app/views/post/index.php.
According to the above rules, calling $this->render(’view’) in a controller app
controllersPostController will actually render the view file @app/views/post/
view.php, while calling $this->render(’_overview’) in that view will render the
view file @app/views/post/_overview.php.
3.7. VIEWS 91
Accessing Data in Views
There are two approaches to access data within a view: push and pull.
By passing the data as the second parameter to the view rendering meth-
ods, you are using the push approach. The data should be represented as
an array of name-value pairs. When the view is being rendered, the PHP
extract() function will be called on this array so that the array is extracted
into variables in the view. For example, the following view rendering code in
a controller will push two variables to the report view: $foo = 1 and $bar = 2.
echo $this->render(’report’, [
’foo’ => 1,
’bar’ => 2,
]);
The pull approach actively retrieves data from the yiibaseView or other
objects accessible in views (e.g. Yii::$app). Using the code below as an
example, within the view you can get the controller object by the expression
$this->context. And as a result, it is possible for you to access any properties
or methods of the controller in the report view, such as the controller ID
shown in the following:
The controller ID is: <?= $this->context->id ?>
?>
The push approach is usually the preferred way of accessing data in views,
because it makes views less dependent on context objects. Its drawback is
that you need to manually build the data array all the time, which could
become tedious and error prone if a view is shared and rendered in different
places.
Sharing Data among Views
The yiibaseView provides the yiibaseView::params property that
you can use to share data among views.
For example, in an about view, you can have the following code which
specifies the current segment of the breadcrumbs.
$this->params[’breadcrumbs’][] = ’About Us’;
Then, in the layout file, which is also a view, you can display the breadcrumbs
using the data passed along yiibaseView::params:
<?= yiiwidgetsBreadcrumbs::widget([
’links’ => isset($this->params[’breadcrumbs’]) ? $this->params[’
breadcrumbs’] : [],
]) ?>
92 CHAPTER 3. APPLICATION STRUCTURE
3.7.3 Layouts
Layouts are a special type of views that represent the common parts of
multiple views. For example, the pages for most Web applications share the
same page header and footer. While you can repeat the same page header
and footer in every view, a better way is to do this once in a layout and
embed the rendering result of a content view at an appropriate place in the
layout.
Creating Layouts
Because layouts are also views, they can be created in the similar way as
normal views. By default, layouts are stored in the directory @app/views/
layouts. For layouts used within a module, they should be stored in the
views/layouts directory under the yiibaseModule::basePath. You may
customize the default layout directory by configuring the yiibaseModule
::layoutPath property of the application or modules.
The following example shows how a layout looks like. Note that for
illustrative purpose, we have greatly simplified the code in the layout. In
practice, you may want to add more content to it, such as head tags, main
menu, etc.
<?php
use yiihelpersHtml;
/* @var $this yiiwebView */
/* @var $content string */
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<?= Html::csrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<header>My Company</header>
<?= $content ?>
<footer>&copy; 2014 by My Company</footer>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>
As you can see, the layout generates the HTML tags that are common to
all pages. Within the <body> section, the layout echoes the $content variable
which represents the rendering result of content views and is pushed into the
3.7. VIEWS 93
layout when yiibaseController::render() is called.
Most layouts should call the following methods like shown in the above
code. These methods mainly trigger events about the rendering process so
that scripts and tags registered in other places can be properly injected into
the places where these methods are called.
• yiibaseView::beginPage(): This method should be called at the
very beginning of the layout. It triggers the yiibaseView::EVENT_BEGIN_PAGE
event which indicates the beginning of a page.
• yiibaseView::endPage(): This method should be called at the end
of the layout. It triggers the yiibaseView::EVENT_END_PAGE event
which indicates the end of a page.
• yiiwebView::head(): This method should be called within the <
head> section of an HTML page. It generates a placeholder which will
be replaced with the registered head HTML code (e.g. link tags, meta
tags) when a page finishes rendering.
• yiiwebView::beginBody(): This method should be called at the be-
ginning of the <body> section. It triggers the yiiwebView::EVENT_BEGIN_BODY
event and generates a placeholder which will be replaced by the re-
gistered HTML code (e.g. JavaScript) targeted at the body begin
position.
• yiiwebView::endBody(): This method should be called at the end
of the <body> section. It triggers the yiiwebView::EVENT_END_BODY
event and generates a placeholder which will be replaced by the re-
gistered HTML code (e.g. JavaScript) targeted at the body end posi-
tion.
Accessing Data in Layouts
Within a layout, you have access to two predefined variables: $this and
$content. The former refers to the yiibaseView component, like in nor-
mal views, while the latter contains the rendering result of a content view
which is rendered by calling the yiibaseController::render() method
in controllers.
If you want to access other data in layouts, you have to use the pull
method as described in the Accessing Data in Views subsection. If you want
to pass data from a content view to a layout, you may use the method
described in the Sharing Data among Views subsection.
Using Layouts
As described in the Rendering in Controllers subsection, when you render a
view by calling the yiibaseController::render() method in a control-
94 CHAPTER 3. APPLICATION STRUCTURE
ler, a layout will be applied to the rendering result. By default, the layout
@app/views/layouts/main.php will be used.
You may use a different layout by configuring either yiibaseApplication
::layout or yiibaseController::layout. The former governs the lay-
out used by all controllers, while the latter overrides the former for individual
controllers. For example, the following code makes the post controller to use
@app/views/layouts/post.php as the layout when rendering its views. Other
controllers, assuming their layout property is untouched, will still use the
default @app/views/layouts/main.php as the layout.
namespace appcontrollers;
use yiiwebController;
class PostController extends Controller
{
public $layout = ’post’;
// ...
}
For controllers belonging to a module, you may also configure the module’s
yiibaseModule::layout property to use a particular layout for these con-
trollers.
Because the layout property may be configured at different levels (control-
lers, modules, application), behind the scene Yii takes two steps to determine
what is the actual layout file being used for a particular controller.
In the first step, it determines the layout value and the context module:
• If the yiibaseController::layout property of the controller is not
null, use it as the layout value and the yiibaseController::module
of the controller as the context module.
• If yiibaseController::layout is null, search through all ancestor
modules (including the application itself) of the controller and find the
first module whose yiibaseModule::layout property is not null.
Use that module and its yiibaseModule::layout value as the con-
text module and the chosen layout value. If such a module cannot be
found, it means no layout will be applied.
In the second step, it determines the actual layout file according to the layout
value and the context module determined in the first step. The layout value
can be:
• a path alias (e.g. @app/views/layouts/main).
• an absolute path (e.g. /main): the layout value starts with a slash. The
actual layout file will be looked for under the application’s yiibase
Application::layoutPath which defaults to @app/views/layouts.
3.7. VIEWS 95
• a relative path (e.g. main): the actual layout file will be looked for under
the context module’s yiibaseModule::layoutPath which defaults
to the views/layouts directory under the yiibaseModule::basePath.
• the boolean value false: no layout will be applied.
If the layout value does not contain a file extension, it will use the default
one .php.
Nested Layouts
Sometimes you may want to nest one layout in another. For example, in
different sections of a Web site, you want to use different layouts, while
all these layouts share the same basic layout that generates the overall
HTML5 page structure. You can achieve this goal by calling yiibaseView
::beginContent() and yiibaseView::endContent() in the child layouts
like the following:
<?php $this->beginContent(’@app/views/layouts/base.php’); ?>
...child layout content here...
<?php $this->endContent(); ?>
As shown above, the child layout content should be enclosed within yii
baseView::beginContent() and yiibaseView::endContent(). The
parameter passed to yiibaseView::beginContent() specifies what is the
parent layout. It can be either a layout file or alias.
Using the above approach, you can nest layouts in more than one levels.
Using Blocks
Blocks allow you to specify the view content in one place while displaying
it in another. They are often used together with layouts. For example, you
can define a block in a content view and display it in the layout.
You call yiibaseView::beginBlock() and yiibaseView::endBlock()
to define a block. The block can then be accessed via $view->blocks[$blockID
], where $blockID stands for a unique ID that you assign to the block when
defining it.
The following example shows how you can use blocks to customize specific
parts of a layout in a content view.
First, in a content view, define one or multiple blocks:
...
<?php $this->beginBlock(’block1’); ?>
...content of block1...
96 CHAPTER 3. APPLICATION STRUCTURE
<?php $this->endBlock(); ?>
...
<?php $this->beginBlock(’block3’); ?>
...content of block3...
<?php $this->endBlock(); ?>
Then, in the layout view, render the blocks if they are available, or display
some default content if a block is not defined.
...
<?php if (isset($this->blocks[’block1’])): ?>
<?= $this->blocks[’block1’] ?>
<?php else: ?>
... default content for block1 ...
<?php endif; ?>
...
<?php if (isset($this->blocks[’block2’])): ?>
<?= $this->blocks[’block2’] ?>
<?php else: ?>
... default content for block2 ...
<?php endif; ?>
...
<?php if (isset($this->blocks[’block3’])): ?>
<?= $this->blocks[’block3’] ?>
<?php else: ?>
... default content for block3 ...
<?php endif; ?>
...
3.7.4 Using View Components
yiibaseView provides many view-related features. While you can get view
components by creating individual instances of yiibaseView or its child
class, in most cases you will mainly use the view application component. You
can configure this component in application configurations like the following:
[
// ...
’components’ => [
’view’ => [
’class’ => ’appcomponentsView’,
],
// ...
],
]
3.7. VIEWS 97
View components provide the following useful view-related features, each
described in more details in a separate section:
• theming: allows you to develop and change the theme for your Web
site.
• fragment caching: allows you to cache a fragment within a Web page.
• client script handling: supports CSS and JavaScript registration and
rendering.
• asset bundle handling: supports registering and rendering of asset
bundles.
• alternative template engines: allows you to use other template engines,
such as Twig14, Smarty15.
You may also frequently use the following minor yet useful features when
you are developing Web pages.
Setting Page Titles
Every Web page should have a title. Normally the title tag is being displayed
in a layout. However, in practice the title is often determined in content
views rather than layouts. To solve this problem, yiiwebView provides
the yiiwebView::title property for you to pass the title information
from content views to layouts.
To make use of this feature, in each content view, you can set the page
title like the following:
<?php
$this->title = ’My page title’;
?>
Then in the layout, make sure you have the following code in the <head>
section:
<title><?= Html::encode($this->title) ?></title>
Registering Meta Tags
Web pages usually need to generate various meta tags needed by different
parties. Like page titles, meta tags appear in the <head> section and are
usually generated in layouts.
If you want to specify what meta tags to generate in content views,
you can call yiiwebView::registerMetaTag() in a content view, like the
following:
14
http://twig.sensiolabs.org/
15
http://www.smarty.net/
98 CHAPTER 3. APPLICATION STRUCTURE
<?php
$this->registerMetaTag([’name’ => ’keywords’, ’content’ => ’yii, framework,
php’]);
?>
The above code will register a “keywords” meta tag with the view component.
The registered meta tag is rendered after the layout finishes rendering. By
then, the following HTML code will be inserted at the place where you
call yiiwebView::head() in the layout and generate the following HTML
code:
<meta name="keywords" content="yii, framework, php">
Note that if you call yiiwebView::registerMetaTag() multiple times, it
will register multiple meta tags, regardless whether the meta tags are the
same or not.
To make sure there is only a single instance of a meta tag type, you can
specify a key as a second parameter when calling the method. For example,
the following code registers two “description” meta tags. However, only the
second one will be rendered.
$this->registerMetaTag([’name’ => ’description’, ’content’ => ’This is my
cool website made with Yii!’], ’description’);
$this->registerMetaTag([’name’ => ’description’, ’content’ => ’This website
is about funny raccoons.’], ’description’);
Registering Link Tags
Like meta tags, link tags are useful in many cases, such as customizing
favicon, pointing to RSS feed or delegating OpenID to another server. You
can work with link tags in the similar way as meta tags by using yiiweb
View::registerLinkTag(). For example, in a content view, you can re-
gister a link tag like follows,
$this->registerLinkTag([
’title’ => ’Live News for Yii’,
’rel’ => ’alternate’,
’type’ => ’application/rss+xml’,
’href’ => ’http://www.yiiframework.com/rss.xml/’,
]);
The code above will result in
<link title="Live News for Yii" rel="alternate" type="application/rss+xml"
href="http://www.yiiframework.com/rss.xml/">
Similar as yiiwebView::registerMetaTag(), you can specify a key when
calling yiiwebView::registerLinkTag() to avoid generated repeated link
tags.
3.7. VIEWS 99
3.7.5 View Events
yiibaseView trigger several events during the view rendering process. You
may respond to these events to inject content into views or process the
rendering results before they are sent to end users.
• yiibaseView::EVENT_BEFORE_RENDER: triggered at the beginning of
rendering a file in a controller. Handlers of this event may set yiibase
ViewEvent::isValid to be false to cancel the rendering process.
• yiibaseView::EVENT_AFTER_RENDER: triggered by the call of yii
baseView::beginPage() in layouts. Handlers of this event may ob-
tain the rendering result through yiibaseViewEvent::output and
may modify this property to change the rendering result.
• yiibaseView::EVENT_BEGIN_PAGE: triggered by the call of yiibase
View::beginPage() in layouts.
• yiibaseView::EVENT_END_PAGE: triggered by the call of yiibase
View::endPage() in layouts.
• yiiwebView::EVENT_BEGIN_BODY: triggered by the call of yiiweb
View::beginBody() in layouts.
• yiiwebView::EVENT_END_BODY: triggered by the call of yiiwebView
::endBody() in layouts.
For example, the following code injects the current date at the end of the
page body:
Yii::$app->view->on(View::EVENT_END_BODY, function () {
echo date(’Y-m-d’);
});
3.7.6 Rendering Static Pages
Static pages refer to those Web pages whose main content are mostly static
without the need of accessing dynamic data pushed from controllers.
You can output static pages by putting their code in the view, and then
using the code like the following in a controller:
public function actionAbout()
{
return $this->render(’about’);
}
If a Web site contains many static pages, it would be very tedious repeating
the similar code many times. To solve this problem, you may introduce a
standalone action called yiiwebViewAction in a controller. For example,
100 CHAPTER 3. APPLICATION STRUCTURE
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
public function actions()
{
return [
’page’ => [
’class’ => ’yiiwebViewAction’,
],
];
}
}
Now if you create a view named about under the directory @app/views/site/
pages, you will be able to display this view by the following URL:
http://localhost/index.php?r=site/page&view=about
The GET parameter view tells yiiwebViewAction which view is requested.
The action will then look for this view under the directory @app/views/site/
pages. You may configure yiiwebViewAction::viewPrefix to change the
directory for searching these views.
3.7.7 Best Practices
Views are responsible for presenting models in the format that end users
desire. In general, views
• should mainly contain presentational code, such as HTML, and simple
PHP code to traverse, format and render data.
• should not contain code that performs DB queries. Such code should
be done in models.
• should avoid direct access to request data, such as $_GET, $_POST. This
belongs to controllers. If request data is needed, they should be pushed
into views by controllers.
• may read model properties, but should not modify them.
To make views more manageable, avoid creating views that are too complex
or contain too much redundant code. You may use the following techniques
to achieve this goal:
• use layouts to represent common presentational sections (e.g. page
header, footer).
3.8. MODULES 101
• divide a complicated view into several smaller ones. The smaller views
can be rendered and assembled into a bigger one using the rendering
methods that we have described.
• create and use widgets as building blocks of views.
• create and use helper classes to transform and format data in views.
3.8 Modules
Modules are self-contained software units that consist of models, views, con-
trollers, and other supporting components. End users can access the con-
trollers of a module when it is installed in application. For these reasons,
modules are often viewed as mini-applications. Modules differ from applic-
ations in that modules cannot be deployed alone and must reside within
applications.
3.8.1 Creating Modules
A module is organized as a directory which is called the yiibaseModule
::basePath of the module. Within the directory, there are sub-directories,
such as controllers, models, views, which hold controllers, models, views, and
other code, just like in an application. The following example shows the
content within a module:
forum/
Module.php the module class file
controllers/ containing controller class files
DefaultController.php the default controller class file
models/ containing model class files
views/ containing controller view and layout files
layouts/ containing layout view files
default/ containing view files for DefaultController
index.php the index view file
Module Classes
Each module should have a module class which extends from yiibase
Module. The class should be located directly under the module’s yiibase
Module::basePath and should be autoloadable. When a module is being
accessed, a single instance of the corresponding module class will be cre-
ated. Like application instances, module instances are used to share data
and components for code within modules.
The following is an example how a module class may look like:
102 CHAPTER 3. APPLICATION STRUCTURE
namespace appmodulesforum;
class Module extends yiibaseModule
{
public function init()
{
parent::init();
$this->params[’foo’] = ’bar’;
// ... other initialization code ...
}
}
If the init() method contains a lot of code initializing the module’s proper-
ties, you may also save them in terms of a configuration and load it with the
following code in init():
public function init()
{
parent::init();
// initialize the module with the configuration loaded from config.php
Yii::configure($this, require(__DIR__ . ’/config.php’));
}
where the configuration file config.php may contain the following content,
similar to that in an application configuration.
<?php
return [
’components’ => [
// list of component configurations
],
’params’ => [
// list of parameters
],
];
Controllers in Modules
When creating controllers in a module, a convention is to put the control-
ler classes under the controllers sub-namespace of the namespace of the
module class. This also means the controller class files should be put in
the controllers directory within the module’s yiibaseModule::basePath.
For example, to create a post controller in the forum module shown in the
last subsection, you should declare the controller class like the following:
namespace appmodulesforumcontrollers;
use yiiwebController;
class PostController extends Controller
{
// ...
3.8. MODULES 103
}
You may customize the namespace of controller classes by configuring the
yiibaseModule::controllerNamespace property. In case when some of
the controllers are out of this namespace, you may make them accessible
by configuring the yiibaseModule::controllerMap property, similar to
what you do in an application.
Views in Modules
Views in a module should be put in the views directory within the module’s
yiibaseModule::basePath. For views rendered by a controller in the
module, they should be put under the directory views/ControllerID, where
ControllerID refers to the controller ID. For example, if the controller class
is PostController, the directory would be views/post within the module’s yii
baseModule::basePath.
A module can specify a layout that is applied to the views rendered by
the module’s controllers. The layout should be put in the views/layouts dir-
ectory by default, and you should configure the yiibaseModule::layout
property to point to the layout name. If you do not configure the layout
property, the application’s layout will be used instead.
3.8.2 Using Modules
To use a module in an application, simply configure the application by listing
the module in the yiibaseApplication::modules property of the applic-
ation. The following code in the application configuration uses the forum
module:
[
’modules’ => [
’forum’ => [
’class’ => ’appmodulesforumModule’,
// ... other configurations for the module ...
],
],
]
The yiibaseApplication::modules property takes an array of module
configurations. Each array key represents a module ID which uniquely identi-
fies the module among all modules in the application, and the corresponding
array value is a configuration for creating the module.
Routes
Like accessing controllers in an application, routes are used to address con-
trollers in a module. A route for a controller within a module must begin
104 CHAPTER 3. APPLICATION STRUCTURE
with the module ID followed by the controller ID and action ID. For ex-
ample, if an application uses a module named forum, then the route forum/
post/index would represent the index action of the post controller in the mod-
ule. If the route only contains the module ID, then the yiibaseModule
::defaultRoute property, which defaults to default, will determine which
controller/action should be used. This means a route forum would represent
the default controller in the forum module.
Accessing Modules
Within a module, you may often need to get the instance of the module
class so that you can access the module ID, module parameters, module
components, etc. You can do so by using the following statement:
$module = MyModuleClass::getInstance();
where MyModuleClass refers to the name of the module class that you are
interested in. The getInstance() method will return the currently requested
instance of the module class. If the module is not requested, the method will
return null. Note that You do not want to manually create a new instance
of the module class because it will be different from the one created by Yii
in response to a request.
Info: When developing a module, you should not assume the
module will use a fixed ID. This is because a module can be
associated with an arbitrary ID when used in an application or
within another module. In order to get the module ID, you should
use the above approach to get the module instance first, and then
get the ID via $module->id.
You may also access the instance of a module using the following approaches:
// get the module whose ID is "forum"
$module = Yii::$app->getModule(’forum’);
// get the module to which the currently requested controller belongs
$module = Yii::$app->controller->module;
The first approach is only useful when you know the module ID, while the
second approach is best used when you know about the controllers being
requested.
Once getting hold of a module instance, you can access parameters or
components registered with the module. For example,
$maxPostCount = $module->params[’maxPostCount’];
3.8. MODULES 105
Bootstrapping Modules
Some modules may need to be run for every request. The yiidebugModule
module is such an example. To do so, list the IDs of such modules in the
yiibaseApplication::bootstrap property of the application.
For example, the following application configuration makes sure the debug
module is always load:
[
’bootstrap’ => [
’debug’,
],
’modules’ => [
’debug’ => ’yiidebugModule’,
],
]
3.8.3 Nested Modules
Modules can be nested in unlimited levels. That is, a module can contain
another module which can contain yet another module. We call the former
parent module while the latter child module. Child modules must be declared
in the yiibaseModule::modules property of their parent modules. For
example,
namespace appmodulesforum;
class Module extends yiibaseModule
{
public function init()
{
parent::init();
$this->modules = [
’admin’ => [
// you should consider using a shorter namespace here!
’class’ => ’appmodulesforummodulesadminModule’,
],
];
}
}
For a controller within a nested module, its route should include the IDs of
all its ancestor module. For example, the route forum/admin/dashboard/index
represents the index action of the dashboard controller in the admin module
which is a child module of the forum module.
106 CHAPTER 3. APPLICATION STRUCTURE
3.8.4 Best Practices
Modules are best used in large applications whose features can be divided
into several groups, each consisting of a set of closely related features. Each
such feature group can be developed as a module which is developed and
maintained by a specific developer or team.
Modules are also a good way of reusing code at the feature group level.
Some commonly used features, such as user management, comment manage-
ment, can all be developed in terms of modules so that they can be reused
easily in future projects.
3.9 Filters
Filters are objects that run before and/or after controller actions. For ex-
ample, an access control filter may run before actions to ensure that they are
allowed to be accessed by particular end users; a content compression filter
may run after actions to compress the response content before sending them
out to end users.
A filter may consist of a pre-filter (filtering logic applied before actions)
and/or a post-filter (logic applied after actions).
3.9.1 Using Filters
Filters are essentially a special kind of behaviors. Therefore, using filters is
the same as using behaviors. You can declare filters in a controller class by
overriding its yiibaseController::behaviors() method like the follow-
ing:
public function behaviors()
{
return [
[
’class’ => ’yiifiltersHttpCache’,
’only’ => [’index’, ’view’],
’lastModified’ => function ($action, $params) {
$q = new yiidbQuery();
return $q->from(’user’)->max(’updated_at’);
},
],
];
}
By default, filters declared in a controller class will be applied to all actions
in that controller. You can, however, explicitly specify which actions the
filter should be applied to by configuring the yiibaseActionFilter::
only property. In the above example, the HttpCache filter only applies to the
index and view actions. You can also configure the yiibaseActionFilter
::except property to blacklist some actions from being filtered.
3.9. FILTERS 107
Besides controllers, you can also declare filters in a module or applica-
tion. When you do so, the filters will be applied to all controller actions
belonging to that module or application, unless you configure the filters’ yii
baseActionFilter::only and yiibaseActionFilter::except proper-
ties like described above.
Note: When declaring filters in modules or applications, you
should use routes instead of action IDs in the yiibaseActionFilter
::only and yiibaseActionFilter::except properties. This
is because action IDs alone cannot fully specify actions within
the scope of a module or application.
When multiple filters are configured for a single action, they are applied
according to the rules described below,
• Pre-filtering
– Apply filters declared in the application in the order they are
listed in behaviors().
– Apply filters declared in the module in the order they are listed
in behaviors().
– Apply filters declared in the controller in the order they are listed
in behaviors().
– If any of the filters cancel the action execution, the filters (both
pre-filters and post-filters) after it will not be applied.
• Running the action if it passes the pre-filtering.
• Post-filtering
– Apply filters declared in the controller in the reverse order they
are listed in behaviors().
– Apply filters declared in the module in the reverse order they are
listed in behaviors().
– Apply filters declared in the application in the reverse order they
are listed in behaviors().
3.9.2 Creating Filters
To create a new action filter, extend from yiibaseActionFilter and
override the yiibaseActionFilter::beforeAction() and/or yiibase
ActionFilter::afterAction() methods. The former will be executed be-
fore an action runs while the latter after an action runs. The return value of
yiibaseActionFilter::beforeAction() determines whether an action
108 CHAPTER 3. APPLICATION STRUCTURE
should be executed or not. If it is false, the filters after this one will be
skipped and the action will not be executed.
The following example shows a filter that logs the action execution time:
namespace appcomponents;
use Yii;
use yiibaseActionFilter;
class ActionTimeFilter extends ActionFilter
{
private $_startTime;
public function beforeAction($action)
{
$this->_startTime = microtime(true);
return parent::beforeAction($action);
}
public function afterAction($action, $result)
{
$time = microtime(true) - $this->_startTime;
Yii::trace("Action ’{$action->uniqueId}’ spent $time second.");
return parent::afterAction($action, $result);
}
}
3.9.3 Core Filters
Yii provides a set of commonly used filters, found primarily under the yii
filters namespace. In the following, we will briefly introduce these filters.
yiifiltersAccessControl
AccessControl provides simple access control based on a set of yiifilters
AccessControl::rules. In particular, before an action is executed, Ac-
cessControl will examine the listed rules and find the first one that matches
the current context variables (such as user IP address, user login status, etc.)
The matching rule will dictate whether to allow or deny the execution of the
requested action. If no rule matches, the access will be denied.
The following example shows how to allow authenticated users to access
the create and update actions while denying all other users from accessing
these two actions.
use yiifiltersAccessControl;
public function behaviors()
{
return [
’access’ => [
’class’ => AccessControl::className(),
3.9. FILTERS 109
’only’ => [’create’, ’update’],
’rules’ => [
// allow authenticated users
[
’allow’ => true,
’roles’ => [’@’],
],
// everything else is denied by default
],
],
];
}
For more details about access control in general, please refer to the Author-
ization section.
Authentication Method Filters
Authentication method filters are used to authenticate a user based using
various methods, such as HTTP Basic Auth16, OAuth 217. These filter
classes are all under the yiifiltersauth namespace.
The following example shows how you can use yiifiltersauthHttpBasicAuth
to authenticate a user using an access token based on HTTP Basic Auth
method. Note that in order for this to work, your yiiwebUser::identityClass
must implement the yiiwebIdentityInterface::findIdentityByAccessToken()
method.
use yiifiltersauthHttpBasicAuth;
public function behaviors()
{
return [
’basicAuth’ => [
’class’ => HttpBasicAuth::className(),
],
];
}
Authentication method filters are commonly used in implementing RESTful
APIs. For more details, please refer to the RESTful Authentication section.
yiifiltersContentNegotiator
ContentNegotiator supports response format negotiation and application
language negotiation. It will try to determine the response format and/or
language by examining GET parameters and Accept HTTP header.
16
http://en.wikipedia.org/wiki/Basic_access_authentication
17
http://oauth.net/2/
110 CHAPTER 3. APPLICATION STRUCTURE
In the following example, ContentNegotiator is configured to support
JSON and XML response formats, and English (United States) and German
languages.
use yiifiltersContentNegotiator;
use yiiwebResponse;
public function behaviors()
{
return [
[
’class’ => ContentNegotiator::className(),
’formats’ => [
’application/json’ => Response::FORMAT_JSON,
’application/xml’ => Response::FORMAT_XML,
],
’languages’ => [
’en-US’,
’de’,
],
],
];
}
Response formats and languages often need to be determined much earlier
during the application lifecycle. For this reason, ContentNegotiator is de-
signed in a way such that it can also be used as a bootstrapping component
besides filter. For example, you may configure it in the application config-
uration like the following:
use yiifiltersContentNegotiator;
use yiiwebResponse;
[
’bootstrap’ => [
[
’class’ => ContentNegotiator::className(),
’formats’ => [
’application/json’ => Response::FORMAT_JSON,
’application/xml’ => Response::FORMAT_XML,
],
’languages’ => [
’en-US’,
’de’,
],
],
],
];
Info: In case the preferred content type and language cannot be
determined from a request, the first format and language listed
in formats and languages will be used.
3.9. FILTERS 111
yiifiltersHttpCache
HttpCache implements client-side caching by utilizing the Last-Modified and
Etag HTTP headers. For example,
use yiifiltersHttpCache;
public function behaviors()
{
return [
[
’class’ => HttpCache::className(),
’only’ => [’index’],
’lastModified’ => function ($action, $params) {
$q = new yiidbQuery();
return $q->from(’user’)->max(’updated_at’);
},
],
];
}
Please refer to the HTTP Caching section for more details about using Ht-
tpCache.
yiifiltersPageCache
PageCache implements server-side caching of whole pages. In the following
example, PageCache is applied to the index action to cache the whole page for
maximum 60 seconds or until the count of entries in the post table changes. It
also stores different versions of the page depending on the chosen application
language.
use yiifiltersPageCache;
use yiicachingDbDependency;
public function behaviors()
{
return [
’pageCache’ => [
’class’ => PageCache::className(),
’only’ => [’index’],
’duration’ => 60,
’dependency’ => [
’class’ => DbDependency::className(),
’sql’ => ’SELECT COUNT(*) FROM post’,
],
’variations’ => [
Yii::$app->language,
]
],
];
}
Please refer to the Page Caching section for more details about using PageCache.
112 CHAPTER 3. APPLICATION STRUCTURE
yiifiltersRateLimiter
RateLimiter implements a rate limiting algorithm based on the leaky bucket
algorithm18. It is primarily used in implementing RESTful APIs. Please
refer to the Rate Limiting section for details about using this filter.
yiifiltersVerbFilter
VerbFilter checks if the HTTP request methods are allowed by the requested
actions. If not allowed, it will throw an HTTP 405 exception. In the following
example, VerbFilter is declared to specify a typical set of allowed request
methods for CRUD actions.
use yiifiltersVerbFilter;
public function behaviors()
{
return [
’verbs’ => [
’class’ => VerbFilter::className(),
’actions’ => [
’index’ => [’get’],
’view’ => [’get’],
’create’ => [’get’, ’post’],
’update’ => [’get’, ’put’, ’post’],
’delete’ => [’post’, ’delete’],
],
],
];
}
yiifiltersCors
Cross-origin resource sharing CORS19 is a mechanism that allows many re-
sources (e.g. fonts, JavaScript, etc.) on a Web page to be requested from
another domain outside the domain the resource originated from. In particu-
lar, JavaScript’s AJAX calls can use the XMLHttpRequest mechanism. Such
“cross-domain” requests would otherwise be forbidden by Web browsers, per
the same origin security policy. CORS defines a way in which the browser
and the server can interact to determine whether or not to allow the cross-
origin request.
The yiifiltersCors should be defined before Authentication / Au-
thorization filters to make sure the CORS headers will always be sent.
use yiifiltersCors;
use yiihelpersArrayHelper;
18
http://en.wikipedia.org/wiki/Leaky_bucket
19
https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS
3.9. FILTERS 113
public function behaviors()
{
return ArrayHelper::merge([
[
’class’ => Cors::className(),
],
], parent::behaviors());
}
The Cors filtering could be tuned using the cors property.
• cors[’Origin’]: array used to define allowed origins. Can be [’*’
] (everyone) or [’http://www.myserver.net’, ’http://www.myotherserver.
com’]. Default to [’*’].
• cors[’Access-Control-Request-Method’]: array of allowed verbs like [’
GET’, ’OPTIONS’, ’HEAD’]. Default to [’GET’, ’POST’, ’PUT’, ’PATCH’, ’
DELETE’, ’HEAD’, ’OPTIONS’].
• cors[’Access-Control-Request-Headers’]: array of allowed headers. Can
be [’*’] all headers or specific ones [’X-Request-With’]. Default to [’*
’].
• cors[’Access-Control-Allow-Credentials’]: define if current request can
be made using credentials. Can be true, false or null (not set). Default
to null.
• cors[’Access-Control-Max-Age’]: define lifetime of pre-flight request. De-
fault to 86400.
For example, allowing CORS for origin : http://www.myserver.net with method
GET, HEAD and OPTIONS :
use yiifiltersCors;
use yiihelpersArrayHelper;
public function behaviors()
{
return ArrayHelper::merge([
[
’class’ => Cors::className(),
’cors’ => [
’Origin’ => [’http://www.myserver.net’],
’Access-Control-Request-Method’ => [’GET’, ’HEAD’, ’OPTIONS’
],
],
],
], parent::behaviors());
}
You may tune the CORS headers by overriding default parameters on a per
action basis. For example adding the Access-Control-Allow-Credentials for
the login action could be done like this :
114 CHAPTER 3. APPLICATION STRUCTURE
use yiifiltersCors;
use yiihelpersArrayHelper;
public function behaviors()
{
return ArrayHelper::merge([
[
’class’ => Cors::className(),
’cors’ => [
’Origin’ => [’http://www.myserver.net’],
’Access-Control-Request-Method’ => [’GET’, ’HEAD’, ’OPTIONS’
],
],
’actions’ => [
’login’ => [
’Access-Control-Allow-Credentials’ => true,
]
]
],
], parent::behaviors());
}
3.10 Widgets
Widgets are reusable building blocks used in views to create complex and
configurable user interface elements in an object-oriented fashion. For ex-
ample, a date picker widget may generate a fancy date picker that allows
users to pick a date as their input. All you need to do is just to insert the
code in a view like the following:
<?php
use yiijuiDatePicker;
?>
<?= DatePicker::widget([’name’ => ’date’]) ?>
There are a good number of widgets bundled with Yii, such as yiiwidgets
ActiveForm, yiiwidgetsMenu, jQuery UI widgets, Twitter Bootstrap
widgets. In the following, we will introduce the basic knowledge about wid-
gets. Please refer to the class API documentation if you want to learn about
the usage of a particular widget.
3.10.1 Using Widgets
Widgets are primarily used in views. You can call the yiibaseWidget::
widget() method to use a widget in a view. The method takes a configura-
tion array for initializing the widget and returns the rendering result of the
widget. For example, the following code inserts a date picker widget which
is configured to use Russian language and keep the input in the from_date
attribute of $model.
3.10. WIDGETS 115
<?php
use yiijuiDatePicker;
?>
<?= DatePicker::widget([
’model’ => $model,
’attribute’ => ’from_date’,
’language’ => ’ru’,
’clientOptions’ => [
’dateFormat’ => ’yy-mm-dd’,
],
]) ?>
Some widgets can take a block of content which should be enclosed between
the invocation of yiibaseWidget::begin() and yiibaseWidget::end().
For example, the following code uses the yiiwidgetsActiveForm widget
to generate a login form. The widget will generate the opening and clos-
ing <form> tags at the place where begin() and end() are called, respectively.
Anything in between will be rendered as is.
<?php
use yiiwidgetsActiveForm;
use yiihelpersHtml;
?>
<?php $form = ActiveForm::begin([’id’ => ’login-form’]); ?>
<?= $form->field($model, ’username’) ?>
<?= $form->field($model, ’password’)->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton(’Login’) ?>
</div>
<?php ActiveForm::end(); ?>
Note that unlike yiibaseWidget::widget() which returns the rendering
result of a widget, the method yiibaseWidget::begin() returns an in-
stance of the widget which you can use to build the widget content.
3.10.2 Creating Widgets
To create a widget, extend from yiibaseWidget and override the yii
baseWidget::init() and/or yiibaseWidget::run() methods. Usu-
ally, the init() method should contain the code that normalizes the widget
properties, while the run() method should contain the code that generates
the rendering result of the widget. The rendering result may be directly
“echoed” or returned as a string by run().
In the following example, HelloWidget HTML-encodes and displays the
content assigned to its message property. If the property is not set, it will
display “Hello World” by default.
116 CHAPTER 3. APPLICATION STRUCTURE
namespace appcomponents;
use yiibaseWidget;
use yiihelpersHtml;
class HelloWidget extends Widget
{
public $message;
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = ’Hello World’;
}
}
public function run()
{
return Html::encode($this->message);
}
}
To use this widget, simply insert the following code in a view:
<?php
use appcomponentsHelloWidget;
?>
<?= HelloWidget::widget([’message’ => ’Good morning’]) ?>
Below is a variant of HelloWidget which takes the content enclosed within the
begin() and end() calls, HTML-encodes it and then displays it.
namespace appcomponents;
use yiibaseWidget;
use yiihelpersHtml;
class HelloWidget extends Widget
{
public function init()
{
parent::init();
ob_start();
}
public function run()
{
$content = ob_get_clean();
return Html::encode($content);
}
}
As you can see, PHP output buffer is started in init() so that any output
between the calls of init() and run() can be captured, processed and returned
3.10. WIDGETS 117
in run().
Info: When you call yiibaseWidget::begin(), a new instance
of the widget will be created and the init() method will be called
at the end of the widget constructor. When you call yiibase
Widget::end(), the run() method will be called whose return
result will be echoed by end().
The following code shows how to use this new variant of HelloWidget:
<?php
use appcomponentsHelloWidget;
?>
<?php HelloWidget::begin(); ?>
content that may contain <tag>’s
<?php HelloWidget::end(); ?>
Sometimes, a widget may need to render a big chunk of content. While you
can embed the content within the run() method, a better approach is to put
it in a view and call yiibaseWidget::render() to render it. For example,
public function run()
{
return $this->render(’hello’);
}
By default, views for a widget should be stored in files in the WidgetPath
/views directory, where WidgetPath stands for the directory containing the
widget class file. Therefore, the above example will render the view file
@app/components/views/hello.php, assuming the widget class is located under
@app/components. You may override the yiibaseWidget::getViewPath()
method to customize the directory containing the widget view files.
3.10.3 Best Practices
Widgets are an object-oriented way of reusing view code.
When creating widgets, you should still follow the MVC pattern. In
general, you should keep logic in widget classes and keep presentation in
views.
Widgets should be designed to be self-contained. That is, when using a
widget, you should be able to just drop it in a view without doing anything
else. This could be tricky if a widget requires external resources, such as
CSS, JavaScript, images, etc. Fortunately, Yii provides the support for asset
bundles, which can be utilized to solve the problem.
When a widget contains view code only, it is very similar to a view. In
fact, in this case, their only difference is that a widget is a redistributable
class, while a view is just a plain PHP script that you would prefer to keep
it within your application.
118 CHAPTER 3. APPLICATION STRUCTURE
3.11 Assets
An asset in Yii is a file that may be referenced in a Web page. It can be a
CSS file, a JavaScript file, an image or video file, etc. Assets are located in
Web-accessible directories and are directly served by Web servers.
It is often preferable to manage assets programmatically. For example,
when you use the yiijuiDatePicker widget in a page, it will automat-
ically include the required CSS and JavaScript files, instead of asking you
to manually find these files and include them. And when you upgrade the
widget to a new version, it will automatically use the new version of the
asset files. In this tutorial, we will describe the powerful asset management
capability provided in Yii.
3.11.1 Asset Bundles
Yii manages assets in the unit of asset bundle. An asset bundle is simply a
collection of assets located in a directory. When you register an asset bundle
in a view, it will include the CSS and JavaScript files in the bundle in the
rendered Web page.
3.11.2 Defining Asset Bundles
Asset bundles are specified as PHP classes extending from yiiwebAssetBundle.
The name of a bundle is simply its corresponding PHP class name which
should be autoloadable. In an asset bundle class, you would typically spe-
cify where the assets are located, what CSS and JavaScript files the bundle
contains, and how the bundle depends on other bundles.
The following code defines the main asset bundle used by the basic ap-
plication template:
<?php
namespace appassets;
use yiiwebAssetBundle;
class AppAsset extends AssetBundle
{
public $basePath = ’@webroot’;
public $baseUrl = ’@web’;
public $css = [
’css/site.css’,
];
public $js = [
];
public $depends = [
’yiiwebYiiAsset’,
’yiibootstrapBootstrapAsset’,
];
3.11. ASSETS 119
}
The above AppAsset class specifies that the asset files are located under the
@webroot directory which is corresponding to the URL @web; the bundle con-
tains a single CSS file css/site.css and no JavaScript file; the bundle depends
on two other bundles: yiiwebYiiAsset and yiibootstrapBootstrapAsset.
More detailed explanation about the properties of yiiwebAssetBundle
can be found in the following:
• yiiwebAssetBundle::sourcePath: specifies the root directory that
contains the asset files in this bundle. This property should be set if the
root directory is not Web accessible. Otherwise, you should set the yii
webAssetBundle::basePath property and yiiwebAssetBundle::
baseUrl, instead. Path aliases can be used here.
• yiiwebAssetBundle::basePath: specifies a Web-accessible direct-
ory that contains the asset files in this bundle. When you specify the
yiiwebAssetBundle::sourcePath property, the asset manager will
publish the assets in this bundle to a Web-accessible directory and
overwrite this property accordingly. You should set this property if
your asset files are already in a Web-accessible directory and do not
need asset publishing. Path aliases can be used here.
• yiiwebAssetBundle::baseUrl: specifies the URL corresponding to
the directory yiiwebAssetBundle::basePath. Like yiiwebAssetBundle
::basePath, if you specify the yiiwebAssetBundle::sourcePath
property, the asset manager will publish the assets and overwrite this
property accordingly. Path aliases can be used here.
• yiiwebAssetBundle::js: an array listing the JavaScript files con-
tained in this bundle. Note that only forward slash “/” should be used
as directory separators. Each JavaScript file can be specified in one of
the following two formats:
– a relative path representing a local JavaScript file (e.g. js/main.
js). The actual path of the file can be determined by prepending
yiiwebAssetManager::basePath to the relative path, and the
actual URL of the file can be determined by prepending yiiweb
AssetManager::baseUrl to the relative path.
– an absolute URL representing an external JavaScript file. For ex-
ample, http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min
.js or //ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js.
• yiiwebAssetBundle::css: an array listing the CSS files contained
in this bundle. The format of this array is the same as that of yiiweb
AssetBundle::js.
120 CHAPTER 3. APPLICATION STRUCTURE
• yiiwebAssetBundle::depends: an array listing the names of the
asset bundles that this bundle depends on (to be explained shortly).
• yiiwebAssetBundle::jsOptions: specifies the options that will be
passed to the yiiwebView::registerJsFile() method when it is
called to register every JavaScript file in this bundle.
• yiiwebAssetBundle::cssOptions: specifies the options that will be
passed to the yiiwebView::registerCssFile() method when it is
called to register every CSS file in this bundle.
• yiiwebAssetBundle::publishOptions: specifies the options that
will be passed to the yiiwebAssetManager::publish() method when
it is called to publish source asset files to a Web directory. This is only
used if you specify the yiiwebAssetBundle::sourcePath property.
Asset Locations
Assets, based on their location, can be classified as:
• source assets: the asset files are located together with PHP source code
which cannot be directly accessed via Web. In order to use source assets
in a page, they should be copied to a Web directory and turned into
the so-called published assets. This process is called asset publishing
which will be described in detail shortly.
• published assets: the asset files are located in a Web directory and can
thus be directly accessed via Web.
• external assets: the asset files are located on a Web server that is
different from the one hosting your Web application.
When defining an asset bundle class, if you specify the yiiwebAssetBundle
::sourcePath property, it means any assets listed using relative paths will be
considered as source assets. If you do not specify this property, it means those
assets are published assets (you should therefore specify yiiwebAssetBundle
::basePath and yiiwebAssetBundle::baseUrl to let Yii know where
they are located.)
It is recommended that you place assets belonging to an application in a
Web directory to avoid the unnecessary asset publishing process. This is why
AppAsset in the prior example specifies yiiwebAssetBundle::basePath in-
stead of yiiwebAssetBundle::sourcePath.
For extensions, because their assets are located together with their source
code in directories that are not Web accessible, you have to specify the
yiiwebAssetBundle::sourcePath property when defining asset bundle
classes for them.
3.11. ASSETS 121
Note: Do not use @webroot/assets as the yiiwebAssetBundle
::sourcePath. This directory is used by default by the yii
webAssetManager to save the asset files published from their
source location. Any content in this directory are considered
temporarily and may be subject to removal.
Asset Dependencies
When you include multiple CSS or JavaScript files in a Web page, they have
to follow certain orders to avoid overriding issues. For example, if you are
using a jQuery UI widget in a Web page, you have to make sure the jQuery
JavaScript file is included before the jQuery UI JavaScript file. We call such
ordering the dependencies among assets.
Asset dependencies are mainly specified through the yiiwebAssetBundle
::depends property. In the AppAsset example, the asset bundle depends on
two other asset bundles: yiiwebYiiAsset and yiibootstrapBootstrapAsset,
which means the CSS and JavaScript files in AppAsset will be included after
those files in the two dependent bundles.
Asset dependencies are transitive. This means if bundle A depends on B
which depends on C, A will depend on C, too.
Asset Options
You can specify the yiiwebAssetBundle::cssOptions and yiiwebAssetBundle
::jsOptions properties to customize the way that CSS and JavaScript files
are included in a page. The values of these properties will be passed to the
yiiwebView::registerCssFile() and yiiwebView::registerJsFile()
methods, respectively, when they are called by the view to include CSS and
JavaScript files.
Note: The options you set in a bundle class apply to every CSS/-
JavaScript file in the bundle. If you want to use different options
for different files, you should create separate asset bundles, and
use one set of options in each bundle.
For example, to conditionally include a CSS file for browsers that are IE9 or
above, you can use the following option:
public $cssOptions = [’condition’ => ’lte IE9’];
This will cause a CSS file in the bundle to be included using the following
HTML tags:
<!--[if lte IE9]>
<link rel="stylesheet" href="path/to/foo.css">
<![endif]-->
122 CHAPTER 3. APPLICATION STRUCTURE
To include a JavaScript file in the head section of a page (by default, JavaS-
cript files are included at the end of the body section), use the following
option:
public $jsOptions = [’position’ => yiiwebView::POS_HEAD];
Bower and NPM Assets
Most JavaScript/CSS package are managed by Bower20 and/or NPM21. If
your application or extension is using such a package, it is recommended
that you follow these steps to manage the assets in the library:
1. Modify the composer.json file of your application or extension and list
the package in the require entry. You should use bower-asset/PackageName
(for Bower packages) or npm-asset/PackageName (for NPM packages) to
refer to the library.
2. Create an asset bundle class and list the JavaScript/CSS files that you
plan to use in your application or extension. You should specify the
yiiwebAssetBundle::sourcePath property as @bower/PackageName or
@npm/PackageName. This is because Composer will install the Bower or
NPM package in the directory corresponding to this alias.
Note: Some packages may put all their distributed files in a sub-
directory. If this is the case, you should specify the subdirect-
ory as the value of yiiwebAssetBundle::sourcePath. For
example, yiiwebJqueryAsset uses @bower/jquery/dist instead
of @bower/jquery.
3.11.3 Using Asset Bundles
To use an asset bundle, register it with a view by calling the yiiweb
AssetBundle::register() method. For example, in a view template you
can register an asset bundle like the following:
use appassetsAppAsset;
AppAsset::register($this); // $this represents the view object
If you are registering an asset bundle in other places, you should provide the
needed view object. For example, to register an asset bundle in a widget
class, you can get the view object by $this->view.
When an asset bundle is registered with a view, behind the scene Yii will
register all its dependent asset bundles. And if an asset bundle is located in a
directory inaccessible through the Web, it will be published to a Web direct-
ory. Later when the view renders a page, it will generate <link> and <script>
20
http://bower.io/
21
https://www.npmjs.org/
3.11. ASSETS 123
tags for the CSS and JavaScript files listed in the registered bundles. The
order of these tags is determined by the dependencies among the registered
bundles and the order of the assets listed in the yiiwebAssetBundle::
css and yiiwebAssetBundle::js properties.
Customizing Asset Bundles
Yii manages asset bundles through an application component named assetManager
which is implemented by yiiwebAssetManager. By configuring the yii
webAssetManager::bundles property, it is possible to customize the be-
havior of an asset bundle. For example, the default yiiwebJqueryAsset
asset bundle uses the jquery.js file from the installed jquery Bower package.
To improve the availability and performance, you may want to use a version
hosted by Google. This can be achieved by configuring assetManager in the
application configuration like the following:
return [
// ...
’components’ => [
’assetManager’ => [
’bundles’ => [
’yiiwebJqueryAsset’ => [
’sourcePath’ => null, // do not publish the bundle
’js’ => [
’//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery
.min.js’,
]
],
],
],
],
];
You can configure multiple asset bundles similarly through yiiwebAssetManager
::bundles. The array keys should be the class names (without the leading
backslash) of the asset bundles, and the array values should be the corres-
ponding configuration arrays.
Tip: You can conditionally choose which assets to use in an asset
bundle. The following example shows how to use jquery.js in the
development environment and jquery.min.js otherwise:
’yiiwebJqueryAsset’ => [
’js’ => [
YII_ENV_DEV ? ’jquery.js’ : ’jquery.min.js’
]
],
You can disable one or multiple asset bundles by associating false with the
names of the asset bundles that you want to disable. When you register
124 CHAPTER 3. APPLICATION STRUCTURE
a disabled asset bundle with a view, none of its dependent bundles will be
registered, and the view will also not include any of the assets in the bundle
in the page it renders. For example, to disable yiiwebJqueryAsset, you
can use the following configuration:
return [
// ...
’components’ => [
’assetManager’ => [
’bundles’ => [
’yiiwebJqueryAsset’ => false,
],
],
],
];
You can also disable all asset bundles by setting yiiwebAssetManager::
bundles as false.
Asset Mapping
Sometimes you want to “fix” incorrect/incompatible asset file paths used in
multiple asset bundles. For example, bundle A uses jquery.min.js of version
1.11.1, and bundle B uses jquery.js of version 2.1.1. While you can fix the
problem by customizing each bundle, an easier way is to use the asset map
feature to map incorrect assets to the desired ones. To do so, configure the
yiiwebAssetManager::assetMap property like the following:
return [
// ...
’components’ => [
’assetManager’ => [
’assetMap’ => [
’jquery.js’ => ’//ajax.googleapis.com/ajax/libs/jquery
/2.1.1/jquery.min.js’,
],
],
],
];
The keys of yiiwebAssetManager::assetMap are the asset names that you
want to fix, and the values are the desired asset paths. When you register an
asset bundle with a view, each relative asset file in its yiiwebAssetBundle
::css and yiiwebAssetBundle::js arrays will be examined against this
map. If any of the keys is found to be the last part of an asset file (which
is prefixed with yiiwebAssetBundle::sourcePath if available), the cor-
responding value will replace the asset and be registered with the view. For
example, an asset file my/path/to/jquery.js matches a key jquery.js.
Note: Only assets specified using relative paths are subject to
asset mapping. And the target asset paths should be either
3.11. ASSETS 125
absolute URLs or paths relative to yiiwebAssetManager::
basePath.
Asset Publishing
As aforementioned, if an asset bundle is located in a directory that is not
Web accessible, its assets will be copied to a Web directory when the bundle
is being registered with a view. This process is called asset publishing, and
is done automatically by the yiiwebAssetManager.
By default, assets are published to the directory @webroot/assets which
corresponds to the URL @web/assets. You may customize this location by con-
figuring the yiiwebAssetManager::basePath and yiiwebAssetManager
::baseUrl properties.
Instead of publishing assets by file copying, you may consider using sym-
bolic links, if your OS and Web server allow. This feature can be enabled by
setting yiiwebAssetManager::linkAssets to be true.
return [
// ...
’components’ => [
’assetManager’ => [
’linkAssets’ => true,
],
],
];
With the above configuration, the asset manager will create a symbolic link
to the source path of an asset bundle when it is being published. This is
faster than file copying and can also ensure that the published assets are
always up-to-date.
3.11.4 Commonly Used Asset Bundles
The core Yii code has defined many asset bundles. Among them, the follow-
ing bundles are commonly used and may be referenced in your application
or extension code.
• yiiwebYiiAsset: It mainly includes the yii.js file which implements
a mechanism of organizing JavaScript code in modules. It also provides
special support for data-method and data-confirm attributes and other
useful features.
• yiiwebJqueryAsset: It includes the jquery.js file from the jQuery
bower package.
• yiibootstrapBootstrapAsset: It includes the CSS file from the
Twitter Bootstrap framework.
126 CHAPTER 3. APPLICATION STRUCTURE
• yiibootstrapBootstrapPluginAsset: It includes the JavaScript
file from the Twitter Bootstrap framework for supporting Bootstrap
JavaScript plugins.
• yiijuiJuiAsset: It includes the CSS and JavaScript files from the
jQuery UI library.
If your code depends on jQuery, jQuery UI or Bootstrap, you should use
these predefined asset bundles rather than creating your own versions. If the
default setting of these bundles do not satisfy your needs, you may customize
them as described in the Customizing Asset Bundle subsection.
3.11.5 Asset Conversion
Instead of directly writing CSS and/or JavaScript code, developers often
write them in some extended syntax and use special tools to convert it into
CSS/JavaScript. For example, for CSS code you may use LESS22 or SCSS23;
and for JavaScript you may use TypeScript24.
You can list the asset files in extended syntax in yiiwebAssetBundle
::css and yiiwebAssetBundle::js in an asset bundle. For example,
class AppAsset extends AssetBundle
{
public $basePath = ’@webroot’;
public $baseUrl = ’@web’;
public $css = [
’css/site.less’,
];
public $js = [
’js/site.ts’,
];
public $depends = [
’yiiwebYiiAsset’,
’yiibootstrapBootstrapAsset’,
];
}
When you register such an asset bundle with a view, the yiiwebAssetManager
will automatically run the pre-processor tools to convert assets in recognized
extended syntax into CSS/JavaScript. When the view finally renders a page,
it will include the CSS/JavaScript files in the page, instead of the original
assets in extended syntax.
Yii uses the file name extensions to identify which extended syntax an
asset is in. By default it recognizes the following syntax and file name ex-
tensions:
22
http://lesscss.org/
23
http://sass-lang.com/
24
http://www.typescriptlang.org/
3.11. ASSETS 127
• LESS25: .less
• SCSS26: .scss
• Stylus27: .styl
• CoffeeScript28: .coffee
• TypeScript29: .ts
Yii relies on the installed pre-processor tools to convert assets. For example,
o use LESS30 you should install the lessc pre-processor command.
You can customize the pre-processor commands and the supported ex-
tended syntax by configuring yiiwebAssetManager::converter like the
following:
return [
’components’ => [
’assetManager’ => [
’converter’ => [
’class’ => ’yiiwebAssetConverter’,
’commands’ => [
’less’ => [’css’, ’lessc {from} {to} --no-color’],
’ts’ => [’js’, ’tsc --out {to} {from}’],
],
],
],
],
];
In the above we specify the supported extended syntax via the yiiweb
AssetConverter::commands property. The array keys are the file extension
names (without leading dot), and the array values are the resulting asset file
extension names and the commands for performing the asset conversion. The
tokens {from} and {to} in the commands will be replaced with the source asset
file paths and the target asset file paths.
Info: There are other ways of working with assets in extended
syntax, besides the one described above. For example, you can
use build tools such as grunt31 to monitor and automatically
convert assets in extended syntax. In this case, you should list
the resulting CSS/JavaScript files in asset bundles rather than
the original files.
25
http://lesscss.org/
26
http://sass-lang.com/
27
http://learnboost.github.io/stylus/
28
http://coffeescript.org/
29
http://www.typescriptlang.org/
30
http://lesscss.org/
31
http://gruntjs.com/
128 CHAPTER 3. APPLICATION STRUCTURE
3.11.6 Combining and Compressing Assets
A Web page can include many CSS and/or JavaScript files. To reduce the
number of HTTP requests and the overall download size of these files, a
common practice is to combine and compress multiple CSS/JavaScript files
into one or very few files, and then include these compressed files instead of
the original ones in the Web pages.
Info: Combining and compressing assets is usually needed when
an application is in production mode. In development mode,
using the original CSS/JavaScript files is often more convenient
for debugging purpose.
In the following, we introduce an approach to combine and compress asset
files without the need of modifying your existing application code.
1. Find out all asset bundles in your application that you plan to combine
and compress.
2. Divide these bundles into one or a few groups. Note that each bundle
can only belong to a single group.
3. Combine/compress the CSS files in each group into a single file. Do
this similarly for the JavaScript files.
4. Define a new asset bundle for each group:
• Set the yiiwebAssetBundle::css and yiiwebAssetBundle
::js properties to be the combined CSS and JavaScript files,
respectively.
• Customize the asset bundles in each group by setting their yii
webAssetBundle::css and yiiwebAssetBundle::js prop-
erties to be empty, and setting their yiiwebAssetBundle::
depends property to be the new asset bundle created for the
group.
Using this approach, when you register an asset bundle in a view, it causes
the automatic registration of the new asset bundle for the group that the
original bundle belongs to. And as a result, the combined/compressed asset
files are included in the page, instead of the original ones.
An Example
Let’s use an example to further explain the above approach.
Assume your application has two pages X and Y. Page X uses asset
bundle A, B and C, while Page Y uses asset bundle B, C and D.
3.11. ASSETS 129
You have two ways to divide these asset bundles. One is to use a single
group to include all asset bundles, the other is to put (A, B, C) in Group
X, and (B, C, D) in Group Y. Which one is better? It depends. The first
way has the advantage that both pages share the same combined CSS and
JavaScript files, which makes HTTP caching more effective. On the other
hand, because the single group contains all bundles, the size of the combined
CSS and JavaScript files will be bigger and thus increase the initial file
transmission time. In this example, we will use the first way, i.e., use a
single group to contain all bundles.
Info: Dividing asset bundles into groups is not trivial task. It
usually requires analysis about the real world traffic data of vari-
ous assets on different pages. At the beginning, you may start
with a single group for simplicity.
Use existing tools (e.g. Closure Compiler32, YUI Compressor33) to combine
and compress CSS and JavaScript files in all the bundles. Note that the files
should be combined in the order that satisfies the dependencies among the
bundles. For example, if Bundle A depends on B which depends on both C
and D, then you should list the asset files starting from C and D, followed
by B and finally A.
After combining and compressing, we get one CSS file and one JavaScript
file. Assume they are named as all-xyz.css and all-xyz.js, where xyz stands
for a timestamp or a hash that is used to make the file name unique to avoid
HTTP caching problem.
We are at the last step now. Configure the yiiwebAssetManager as
follows in the application configuration:
return [
’components’ => [
’assetManager’ => [
’bundles’ => [
’all’ => [
’class’ => ’yiiwebAssetBundle’,
’basePath’ => ’@webroot/assets’,
’baseUrl’ => ’@web/assets’,
’css’ => [’all-xyz.css’],
’js’ => [’all-xyz.js’],
],
’A’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]],
’B’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]],
’C’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]],
’D’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]],
],
],
],
];
32
https://developers.google.com/closure/compiler/
33
https://github.com/yui/yuicompressor/
130 CHAPTER 3. APPLICATION STRUCTURE
As explained in the Customizing Asset Bundles subsection, the above config-
uration changes the default behavior of each bundle. In particular, Bundle
A, B, C and D no longer have any asset files. They now all depend on
the all bundle which contains the combined all-xyz.css and all-xyz.js files.
Consequently, for Page X, instead of including the original source files from
Bundle A, B and C, only these two combined files will be included; the same
thing happens to Page Y.
There is one final trick to make the above approach work more smoothly.
Instead of directly modifying the application configuration file, you may put
the bundle customization array in a separate file and conditionally include
this file in the application configuration. For example,
return [
’components’ => [
’assetManager’ => [
’bundles’ => require(__DIR__ . ’/’ . (YII_ENV_PROD ? ’assets-
prod.php’ : ’assets-dev.php’)),
],
],
];
That is, the asset bundle configuration array is saved in assets-prod.php for
production mode, and assets-dev.php for non-production mode.
Using the asset Command
Yii provides a console command named asset to automate the approach that
we just described.
To use this command, you should first create a configuration file to
describe what asset bundles should be combined and how they should be
grouped. You can use the asset/template sub-command to generate a tem-
plate first and then modify it to fit for your needs.
yii asset/template assets.php
The command generates a file named assets.php in the current directory.
The content of this file looks like the following:
<?php
/**
* Configuration file for the "yii asset" console command.
* Note that in the console environment, some path aliases like ’@webroot’
and ’@web’ may not exist.
* Please define these missing path aliases.
*/
return [
// Adjust command/callback for JavaScript files compressing:
’jsCompressor’ => ’java -jar compiler.jar --js {from} --js_output_file {
to}’,
// Adjust command/callback for CSS files compressing:
’cssCompressor’ => ’java -jar yuicompressor.jar --type css {from} -o {to
}’,
3.11. ASSETS 131
// The list of asset bundles to compress:
’bundles’ => [
// ’yiiwebYiiAsset’,
// ’yiiwebJqueryAsset’,
],
// Asset bundle for compression output:
’targets’ => [
’all’ => [
’class’ => ’yiiwebAssetBundle’,
’basePath’ => ’@webroot/assets’,
’baseUrl’ => ’@web/assets’,
’js’ => ’js/all-{hash}.js’,
’css’ => ’css/all-{hash}.css’,
],
],
// Asset manager configuration:
’assetManager’ => [
],
];
You should modify this file and specify which bundles you plan to combine in
the bundles option. In the targets option you should specify how the bundles
should be divided into groups. You can specify one or multiple groups, as
aforementioned.
Note: Because the alias @webroot and @web are not available in
the console application, you should explicitly define them in the
configuration.
JavaScript files are combined, compressed and written to js/all-{hash}.js
where {hash} is replaced with the hash of the resulting file.
The jsCompressor and cssCompressor options specify the console commands
or PHP callbacks for performing JavaScript and CSS combining/compress-
ing. By default Yii uses Closure Compiler34 for combining JavaScript files
and YUI Compressor35 for combining CSS files. You should install tools
manually or adjust these options to use your favorite tools.
With the configuration file, you can run the asset command to combine
and compress the asset files and then generate a new asset bundle configur-
ation file assets-prod.php:
yii asset assets.php config/assets-prod.php
The generated configuration file can be included in the application configur-
ation, like described in the last subsection.
Info: Using the asset command is not the only option to automate
the asset combining and compressing process. You can use the
excellent task runner tool grunt36 to achieve the same goal.
34
https://developers.google.com/closure/compiler/
35
https://github.com/yui/yuicompressor/
36
http://gruntjs.com/
132 CHAPTER 3. APPLICATION STRUCTURE
3.12 Extensions
Extensions are redistributable software packages specifically designed to be
used in Yii applications and provide ready-to-use features. For example,
the yiisoft/yii2-debug extension adds a handy debug toolbar at the bottom
of every page in your application to help you more easily grasp how the
pages are generated. You can use extensions to accelerate your development
process. You can also package your code as extensions to share with other
people your great work.
Info: We use the term “extension” to refer to Yii-specific software
packages. For general purpose software packages that can be used
without Yii, we will refer to them using the term “package” or
“library”.
3.12.1 Using Extensions
To use an extension, you need to install it first. Most extensions are distrib-
uted as Composer37 packages which can be installed by taking the following
two simple steps:
1. modify the composer.json file of your application and specify which ex-
tensions (Composer packages) you want to install.
2. run php composer.phar install to install the specified extensions.
Note that you may need to install Composer38 if you do not have it.
By default, Composer installs packages registered on Packagist39 - the
biggest repository for open source Composer packages. You can look for
extensions on Packagist. You may also create your own repository40 and
configure Composer to use it. This is useful if you are developing closed
open extensions and want to share within your projects.
Extensions installed by Composer are stored in the BasePath/vendor direct-
ory, where BasePath refers to the application’s base path. Because Composer
is a dependency manager, when it installs a package, it will also install all
its dependent packages.
For example, to install the yiisoft/yii2-imagine extension, modify your
composer.json like the following:
{
// ...
37
https://getcomposer.org/
38
https://getcomposer.org/
39
https://packagist.org/
40
https://getcomposer.org/doc/05-repositories.md#repository
3.12. EXTENSIONS 133
"require": {
// ... other dependencies
"yiisoft/yii2-imagine": "*"
}
}
After the installation, you should see the directory yiisoft/yii2-imagine under
BasePath/vendor. You should also see another directory imagine/imagine which
contains the installed dependent package.
Info: The yiisoft/yii2-imagine is a core extension developed and
maintained by the Yii developer team. All core extensions are
hosted on Packagist41 and named like yiisoft/yii2-xyz, where xyz
varies for different extensions.
Now you can use the installed extensions like they are part of your applic-
ation. The following example shows how you can use the yiiimagineImage
class provided by the yiisoft/yii2-imagine extension:
use Yii;
use yiiimagineImage;
// generate a thumbnail image
Image::thumbnail(’@webroot/img/test-image.jpg’, 120, 120)
->save(Yii::getAlias(’@runtime/thumb-test-image.jpg’), [’quality’ =>
50]);
Info: Extension classes are autoloaded by the Yii class auto-
loader.
Installing Extensions Manually
In some rare occasions, you may want to install some or all extensions manu-
ally, rather than relying on Composer. To do so, you should
1. download the extension archive files and unpack them in the vendor
directory.
2. install the class autoloaders provided by the extensions, if any.
3. download and install all dependent extensions as instructed.
If an extension does not have a class autoloader but follows the PSR-4 stand-
ard42, you may use the class autoloader provided by Yii to autoload the
extension classes. All you need to do is just to declare a root alias for the
41
https://packagist.org/
42
http://www.php-fig.org/psr/psr-4/
134 CHAPTER 3. APPLICATION STRUCTURE
extension root directory. For example, assuming you have installed an ex-
tension in the directory vendor/mycompany/myext, and the extension classes are
under the myext namespace, then you can include the following code in your
application configuration:
[
’aliases’ => [
’@myext’ => ’@vendor/mycompany/myext’,
],
]
3.12.2 Creating Extensions
You may consider creating an extension when you feel the need to share with
other people your great code. An extension can contain any code you like,
such as a helper class, a widget, a module, etc.
It is recommended that you create an extension in terms of a Composer
package43 so that it can be more easily installed and used by other users,
liked described in the last subsection.
Below are the basic steps you may follow to create an extension as a
Composer package.
1. Create a project for your extension and host it on a VCS repository,
such as github.com44. The development and maintenance work about
the extension should be done on this repository.
2. Under the root directory of the project, create a file named composer.
json as required by Composer. Please refer to the next subsection for
more details.
3. Register your extension with a Composer repository, such as Pack-
agist45, so that other users can find and install your extension using
Composer.
composer.json
Each Composer package must have a composer.json file in its root directory.
The file contains the metadata about the package. You may find complete
specification about this file in the Composer Manual46. The following ex-
ample shows the composer.json file for the yiisoft/yii2-imagine extension:
{
// package name
"name": "yiisoft/yii2-imagine",
43
https://getcomposer.org/
44
https://github.com
45
https://packagist.org/
46
https://getcomposer.org/doc/01-basic-usage.md#composer-json-project-setup
3.12. EXTENSIONS 135
// package type
"type": "yii2-extension",
"description": "The Imagine integration for the Yii framework",
"keywords": ["yii2", "imagine", "image", "helper"],
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3
Aimagine",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"authors": [
{
"name": "Antonio Ramirez",
"email": "amigo.cobos@gmail.com"
}
],
// package dependencies
"require": {
"yiisoft/yii2": "*",
"imagine/imagine": "v0.5.0"
},
// class autoloading specs
"autoload": {
"psr-4": {
"yiiimagine": ""
}
}
}
Package Name Each Composer package should have a package name
which uniquely identifies the package among all others. The format of pack-
age names is vendorName/projectName. For example, in the package name
yiisoft/yii2-imagine, the vendor name and the project name are yiisoft and
yii2-imagine, respectively.
Do NOT use yiisoft as vendor name as it is reserved for use by the Yii
core code.
We recommend you prefix yii2- to the project name for packages rep-
resenting Yii 2 extensions, for example, myname/yii2-mywidget. This will allow
users to more easily tell whether a package is a Yii 2 extension.
Package Type It is important that you specify the package type of your
extension as yii2-extension so that the package can be recognized as a Yii
extension when being installed.
136 CHAPTER 3. APPLICATION STRUCTURE
When a user runs php composer.phar install to install an extension, the
file vendor/yiisoft/extensions.php will be automatically updated to include
the information about the new extension. From this file, Yii applications
can know which extensions are installed (the information can be accessed
via yiibaseApplication::extensions.
Dependencies Your extension depends on Yii (of course). So you should
list it (yiisoft/yii2) in the require entry in composer.json. If your extension
also depends on other extensions or third-party libraries, you should list
them as well. Make sure you also list appropriate version constraints (e.g.
1.*, @stable) for each dependent package. Use stable dependencies when your
extension is released in a stable version.
Most JavaScript/CSS packages are managed using Bower47 and/or NPM48,
instead of Composer. Yii uses the Composer asset plugin49 to enable man-
aging these kinds of packages through Composer. If your extension depends
on a Bower package, you can simply list the dependency in composer.json like
the following:
{
// package dependencies
"require": {
"bower-asset/jquery": ">=1.11.*"
}
}
The above code states that the extension depends on the jquery Bower pack-
age. In general, you can use bower-asset/PackageName to refer to a Bower pack-
age in composer.json, and use npm-asset/PackageName to refer to a NPM package.
When Composer installs a Bower or NPM package, by default the package
content will be installed under the @vendor/bower/PackageName and @vendor/npm
/Packages directories, respectively. These two directories can also be referred
to using the shorter aliases @bower/PackageName and @npm/PackageName.
For more details about asset management, please refer to the Assets
section.
Class Autoloading In order for your classes to be autoloaded by the Yii
class autoloader or the Composer class autoloader, you should specify the
autoload entry in the composer.json file, like shown below:
{
// ....
"autoload": {
"psr-4": {
47
http://bower.io/
48
https://www.npmjs.org/
49
https://github.com/francoispluchino/composer-asset-plugin
3.12. EXTENSIONS 137
"yiiimagine": ""
}
}
}
You may list one or multiple root namespaces and their corresponding file
paths.
When the extension is installed in an application, Yii will create for each
listed root namespace an alias that refers to the directory corresponding to
the namespace. For example, the above autoload declaration will correspond
to an alias named @yii/imagine.
Recommended Practices
Because extensions are meant to be used by other people, you often need
to take extra development effort. Below we introduce some common and
recommended practices in creating high quality extensions.
Namespaces To avoid name collisions and make the classes in your ex-
tension autoloadable, you should use namespaces and name the classes in
your extension by following the PSR-4 standard50 or PSR-0 standard51.
You class namespaces should start with vendorNameextensionName, where
extensionName is similar to the project name in the package name except that
it should not contain the yii2- prefix. For example, for the yiisoft/yii2-
imagine extension, we use yiiimagine as the namespace its classes.
Do not use yii, yii2 or yiisoft as vendor name. These names are reserved
for use by the Yii core code.
Bootstrapping Classes Sometimes, you may want your extension to
execute some code during the bootstrapping process stage of an applica-
tion. For example, your extension may want to respond to the application’s
beginRequest event to adjust some environment settings. While you can in-
struct users of the extension to explicitly attach your event handler in the
extension to the beginRequest event, a better way is to do this automatically.
To achieve this goal, you can create a so-called bootstrapping class by
implementing yiibaseBootstrapInterface. For example,
namespace mynamemywidget;
use yiibaseBootstrapInterface;
use yiibaseApplication;
class MyBootstrapClass implements BootstrapInterface
{
public function bootstrap($app)
50
http://www.php-fig.org/psr/psr-4/
51
http://www.php-fig.org/psr/psr-0/
138 CHAPTER 3. APPLICATION STRUCTURE
{
$app->on(Application::EVENT_BEFORE_REQUEST, function () {
// do something here
});
}
}
You then list this class in the composer.json file of your extension like follows,
{
// ...
"extra": {
"bootstrap": "mynamemywidgetMyBootstrapClass"
}
}
When the extension is installed in an application, Yii will automatically in-
stantiate the bootstrapping class and call its yiibaseBootstrapInterface
::bootstrap() method during the bootstrapping process for every request.
Working with Databases Your extension may need to access databases.
Do not assume that the applications that use your extension will always use
Yii::$db as the DB connection. Instead, you should declare a db property
for the classes that require DB access. The property will allow users of your
extension to customize which DB connection they would like your extension
to use. As an example, you may refer to the yiicachingDbCache class
and see how it declares and uses the db property.
If your extension needs to create specific DB tables or make changes to
DB schema, you should
• provide migrations to manipulate DB schema, rather than using plain
SQL files;
• try to make the migrations applicable to different DBMS;
• avoid using Active Record in the migrations.
Using Assets If your extension is a widget or a module, chances are that
it may require some assets to work. For example, a module may display
some pages which contain images, JavaScript, and CSS. Because the files of
an extension are all under the same directory which is not Web accessible
when installed in an application, you have two choices to make the asset files
directly accessible via Web:
• ask users of the extension to manually copy the asset files to a specific
Web-accessible folder;
3.12. EXTENSIONS 139
• declare an asset bundle and rely on the asset publishing mechanism
to automatically copy the files listed in the asset bundle to a Web-
accessible folder.
We recommend you use the second approach so that your extension can be
more easily used by other people. Please refer to the [Assets] section for
more details about how to work with assets in general.
Internationalization and Localization Your extension may be used
by applications supporting different languages! Therefore, if your extension
displays content to end users, you should try to internationalize and localize
it. In particular,
• If the extension displays messages intended for end users, the messages
should be wrapped into Yii::t() so that they can be translated. Mes-
sages meant for developers (such as internal exception messages) do
not need to be translated.
• If the extension displays numbers, dates, etc., they should be formatted
using yiii18nFormatter with appropriate formatting rules.
For more details, please refer to the Internationalization section.
Testing You want your extension to run flawlessly without bringing prob-
lems to other people. To reach this goal, you should test your extension
before releasing it to public.
It is recommended that you create various test cases to cover your exten-
sion code rather than relying on manual tests. Each time before you release
a new version of your extension, you may simply run these test cases to make
sure everything is in good shape. Yii provides testing support, which can
help you to more easily write unit tests, acceptance tests and functionality
tests. For more details, please refer to the Testing section.
Versioning You should give each release of your extension a version num-
ber (e.g. 1.0.1). We recommend you follow the semantic versioning52 prac-
tice when determining what version numbers should be used.
Releasing To let other people know your extension, you need to release
it to public.
If it is the first time you release an extension, you should register it on
a Composer repository, such as Packagist53. After that, all you need to do
is simply creating a release tag (e.g. v1.0.1) on the VCS repository of your
52
http://semver.org
53
https://packagist.org/
140 CHAPTER 3. APPLICATION STRUCTURE
extension and notify the Composer repository about the new release. People
will then be able to find the new release, and install or update the extension
through the Composer repository.
In the releases of your extension, besides code files you should also con-
sider including the followings to help other people learn about and use your
extension:
• A readme file in the package root directory: it describes what your
extension does and how to install and use it. We recommend you write
it in Markdown54 format and name the file as readme.md.
• A changelog file in the package root directory: it lists what changes
are made in each release. The file may be written in Markdown format
and named as changelog.md.
• An upgrade file in the package root directory: it gives the instructions
on how to upgrade from older releases of the extension. The file may
be written in Markdown format and named as upgrade.md.
• Tutorials, demos, screenshots, etc.: these are needed if your extension
provides many features that cannot be fully covered in the readme file.
• API documentation: your code should be well documented to allow
other people more easily read and understand it. You may refer to the
Object class file55 to learn how to document your code.
Info: Your code comments can be written in Markdown format.
The yiisoft/yii2-apidoc extension provides a tool for you to gen-
erate pretty API documentation based on your code comments.
Info: While not a requirement, we suggest your extension adhere
to certain coding styles. You may refer to the core framework
code style56.
3.12.3 Core Extensions
Yii provides the following core extensions that are developed and maintained
by the Yii developer team. They are all registered on Packagist57 and can
be easily installed as described in the Using Extensions subsection.
• yiisoft/yii2-apidoc58: provides an extensible and high-performance API
documentation generator. It is also used to generate the core frame-
work API documentation.
54
http://daringfireball.net/projects/markdown/
55
https://github.com/yiisoft/yii2/blob/master/framework/base/Object.php
56
https://github.com/yiisoft/yii2/wiki/Core-framework-code-style
57
https://packagist.org/
58
https://github.com/yiisoft/yii2-apidoc
3.12. EXTENSIONS 141
• yiisoft/yii2-authclient59: provides a set of commonly used auth clients,
such as Facebook OAuth2 client, GitHub OAuth2 client.
• yiisoft/yii2-bootstrap60: provides a set of widgets that encapsulate the
Bootstrap61 components and plugins.
• yiisoft/yii2-codeception62: provides testing support based on Codecep-
tion63.
• yiisoft/yii2-debug64: provides debugging support for Yii applications.
When this extension is used, a debugger toolbar will appear at the
bottom of every page. The extension also provides a set of standalone
pages to display more detailed debug information.
• yiisoft/yii2-elasticsearch65: provides the support for using Elasticsearch66.
It includes basic querying/search support and also implements the Act-
ive Record pattern that allows you to store active records in Elastic-
search.
• yiisoft/yii2-faker67: provides the support for using Faker68 to generate
fake data for you.
• yiisoft/yii2-gii69: provides a Web-based code generator that is highly
extensible and can be used to quickly generate models, forms, modules,
CRUD, etc.
• yiisoft/yii2-imagine70: provides commonly used image manipulation
functions based on Imagine71.
• yiisoft/yii2-jui72: provides a set of widgets that encapsulate the JQuery
UI73 interactions and widgets.
59
https://github.com/yiisoft/yii2-authclient
60
https://github.com/yiisoft/yii2-bootstrap
61
http://getbootstrap.com/
62
https://github.com/yiisoft/yii2-codeception
63
http://codeception.com/
64
https://github.com/yiisoft/yii2-debug
65
https://github.com/yiisoft/yii2-elasticsearch
66
http://www.elasticsearch.org/
67
https://github.com/yiisoft/yii2-faker
68
https://github.com/fzaninotto/Faker
69
https://github.com/yiisoft/yii2-gii
70
https://github.com/yiisoft/yii2-imagine
71
http://imagine.readthedocs.org/
72
https://github.com/yiisoft/yii2-jui
73
http://jqueryui.com/
142 CHAPTER 3. APPLICATION STRUCTURE
• yiisoft/yii2-mongodb74: provides the support for using MongoDB75.
It includes features such as basic query, Active Record, migrations,
caching, code generation, etc.
• yiisoft/yii2-redis76: provides the support for using redis77. It includes
features such as basic query, Active Record, caching, etc.
• yiisoft/yii2-smarty78: provides a template engine based on Smarty79.
• yiisoft/yii2-sphinx80: provides the support for using Sphinx81. It in-
cludes features such as basic query, Active Record, code generation,
etc.
• yiisoft/yii2-swiftmailer82: provides email sending features based on
swiftmailer83.
• yiisoft/yii2-twig84: provides a template engine based on Twig85.
74
https://github.com/yiisoft/yii2-mongodb
75
http://www.mongodb.org/
76
https://github.com/yiisoft/yii2-redis
77
http://redis.io/
78
https://github.com/yiisoft/yii2-smarty
79
http://www.smarty.net/
80
https://github.com/yiisoft/yii2-sphinx
81
http://sphinxsearch.com
82
https://github.com/yiisoft/yii2-swiftmailer
83
http://swiftmailer.org/
84
https://github.com/yiisoft/yii2-twig
85
http://twig.sensiolabs.org/
Chapter 4
Handling Requests
4.1 Overview
Each time when a Yii application handles a request, it undergoes a similar
workflow.
1. A user makes a request to the entry script web/index.php.
2. The entry script loads the application configuration and creates an
application instance to handle the request.
3. The application resolves the requested route with the help of the re-
quest application component.
4. The application creates a controller instance to handle the request.
5. The controller creates an action instance and performs the filters for
the action.
6. If any filter fails, the action is cancelled.
7. If all filters pass, the action is executed.
8. The action loads a data model, possibly from a database.
9. The action renders a view, providing it with the data model.
10. The rendered result is returned to the response application component.
11. The response component sends the rendered result to the user’s browser.
The following diagram shows how an application handles a request.
143
144 CHAPTER 4. HANDLING REQUESTS
In this section, we will describe in detail how some of these steps work.
4.2 Bootstrapping
Bootstrapping refers to the process of preparing the environment before an
application starts to resolve and process an incoming request. Bootstrapping
is done in two places: the entry script and the application.
In the entry script, class autoloaders for different libraries are registered.
This includes the Composer autoloader through its autoload.php file and the
Yii autoloader through its Yii class file. The entry script then loads the
application configuration and creates an application instance.
In the constructor of the application, the following bootstrapping work
are done:
1. yiibaseApplication::preInit() is called, which configures some
high priority application properties, such as yiibaseApplication::
basePath.
2. Register the yiibaseApplication::errorHandler.
3. Initialize application properties using the given application configura-
tion.
4. yiibaseApplication::init() is called which in turn calls yiibase
Application::bootstrap() to run bootstrapping components.
4.3. ROUTING 145
• Include the extension manifest file vendor/yiisoft/extensions.php.
• Create and run bootstrap components declared by extensions.
• Create and run application components and/or modules that are
declared in the application’s bootstrap property.
Because the bootstrapping work has to be done before handling every re-
quest, it is very important to keep this process light and optimize it as much
as possible.
Try not to register too many bootstrapping components. A bootstrap-
ping component is needed only if it wants to participate the whole life cycle
of requesting handling. For example, if a module needs to register additional
URL parsing rules, it should be listed in the bootstrap property so that the
new URL rules can take effect before they are used to resolve requests.
In production mode, enable bytecode cache, such as APC, to minimize
the time needed for including and parsing PHP files.
Some large applications have very complex application configurations
which are divided into many smaller configuration files. If this is the case,
consider caching the whole configuration array and loading it directly from
cache before creating the application instance in the entry script.
4.3 Routing
When the yiiwebApplication::run() method is called by the entry script,
the first thing it does is to resolve the incoming request and instantiate an
appropriate controller action to handle the request. This process is called
routing.
4.3.1 Resolving Route
The first step of routing is to parse the incoming request into a route which,
as described in the Controllers section, is used to address a controller ac-
tion. This is done by yiiwebRequest::resolve() method of the request
application component. The method invokes the URL manager to do the
actual request parsing work.
By default, if the incoming request contains a GET parameter named r, its
value will be considered as the route. However, if the yiiwebUrlManager
::enablePrettyUrl is enabled, more work will be done to determine the
requested route. For more details, please refer to the URL Parsing and
Generation section.
In case a route cannot be determined, the request component will throw
a yiiwebNotFoundHttpException.
146 CHAPTER 4. HANDLING REQUESTS
Default Route
If an incoming request does not specify a route, which often happens to
the request for homepages, the route specified by yiiwebApplication::
defaultRoute will be used. The default value of this property is site/index,
which refers to the index action of the site controller. You may customize
this property in the application configuration like the following:
return [
// ...
’defaultRoute’ => ’main/index’,
];
catchAll Route
Sometimes, you may want to put your Web application in maintenance mode
temporarily and display the same informational page for all requests. There
are many ways to accomplish this goal. But one of the simplest ways is to
configure the yiiwebApplication::catchAll property like the following
in the application configuration:
return [
// ...
’catchAll’ => [’site/offline’],
];
The catchAll property should take an array whose first element specifies a
route, and the rest of the elements (name-value pairs) specify the parameters
to be bound to the action.
When the catchAll property is set, it will replace any route resolved from
the incoming requests. With the above configuration, the same site/offline
action will be used to handle all incoming requests.
4.3.2 Creating Action
Once the requested route is determined, the next step is to create the action
object corresponding to the route.
The route is broken down into multiple parts by the slashes in it. For
example, site/index will be broken into site and index. Each part is an ID
which may refer to a module, a controller or an action.
Starting from the first part in the route, the application conducts the
following steps to create modules (if any), the controller and the action:
1. Set the application as the current module.
2. Check if the yiibaseModule::controllerMap of the current module
contains the current ID. If so, a controller object will be created ac-
cording to the controller configuration found in the map, and do Step
5 with the rest parts of the route.
4.4. REQUESTS 147
3. Check if the ID refers to a module listed in the yiibaseModule::
modules property of the current module. If so, a module is created
according to the configuration found in the module list, and do Step 2
with the next part in the route under the context of the newly created
module.
4. Treat the ID as a controller ID and create a controller object. Do the
next step with the rest part of the route.
5. The controller looks for the current ID in its yiibaseController::
actions(). If found, it creates an action according to the configuration
found in the map. Otherwise, the controller will attempt to create an
inline action which is defined by an action method corresponding to
the current ID.
Among the above steps, if any error occurs, a yiiwebNotFoundHttpException
will be thrown, indicating failure of the routing.
4.4 Requests
Requests made to an application are represented in terms of yiiwebRequest
objects which provide information such as request parameters, HTTP head-
ers, cookies, etc. For a given request, you can get access to the corresponding
request object via the request application component. In this section, we will
describe how you can make use of this component in your applications.
4.4.1 Request Parameters
To get request parameters, you can call yiiwebRequest::get() and yii
webRequest::post() methods of the request component. They return the
values of $_GET and $_POST, respectively. For example,
$request = Yii::$app->request;
$get = $request->get();
// equivalent to: $get = $_GET;
$id = $request->get(’id’);
// equivalent to: $id = isset($_GET[’id’]) ? $_GET[’id’] : null;
$id = $request->get(’id’, 1);
// equivalent to: $id = isset($_GET[’id’]) ? $_GET[’id’] : 1;
$post = $request->post();
// equivalent to: $post = $_POST;
$name = $request->post(’name’);
// equivalent to: $name = isset($_POST[’name’]) ? $_POST[’name’] : null;
148 CHAPTER 4. HANDLING REQUESTS
$name = $request->post(’name’, ’’);
// equivalent to: $name = isset($_POST[’name’]) ? $_POST[’name’] : ’’;
Info: Instead of directly accessing $_GET and $_POST to retrieve
the request parameters, it is recommended that you get them via
the request component like shown above. This will make writing
tests easier because you can create a mock request component
with faked request data.
When implementing RESTful APIs, you often need to retrieve parameters
that are submitted via PUT, PATCH or other request methods. You can get
these parameters by calling the yiiwebRequest::getBodyParam() meth-
ods. For example,
$request = Yii::$app->request;
// returns all parameters
$params = $request->bodyParams;
// returns the parameter "id"
$param = $request->getBodyParam(’id’);
Info: Unlike GET parameters, parameters submitted via POST, PUT,
PATCH etc. are sent in the request body. The request component
will parse these parameters when you access them through the
methods described above. You can customize the way how these
parameters are parsed by configuring the yiiwebRequest::
parsers property.
4.4.2 Request Methods
You can get the HTTP method used by the current request via the ex-
pression Yii::$app->request->method. A whole set of boolean properties are
also provided for you to check if the current method is of certain type. For
example,
$request = Yii::$app->request;
if ($request->isAjax) { // the request is an AJAX request }
if ($request->isGet) { // the request method is GET }
if ($request->isPost) { // the request method is POST }
if ($request->isPut) { // the request method is PUT }
4.4.3 Request URLs
The request component provides many ways of inspecting the currently re-
quested URL.
4.4. REQUESTS 149
Assuming the URL being requested is http://example.com/admin/index.php
/product?id=100, you can get various parts of this URL as summarized in the
following:
• yiiwebRequest::url: returns /admin/index.php/product?id=100, which
is the URL without the host info part.
• yiiwebRequest::absoluteUrl: returns http://example.com/admin/index
.php/product?id=100, which is the whole URL including the host info
part.
• yiiwebRequest::hostInfo: returns http://example.com, which is the
host info part of the URL.
• yiiwebRequest::pathInfo: returns /product, which is the part after
the entry script and before the question mark (query string).
• yiiwebRequest::queryString: returns id=100, which is the part
after the question mark.
• yiiwebRequest::baseUrl: returns /admin, which is the part after
the host info and before the entry script name.
• yiiwebRequest::scriptUrl: returns /admin/index.php, which is the
URL without path info and query string.
• yiiwebRequest::serverName: returns example.com, which is the host
name in the URL.
• yiiwebRequest::serverPort: returns 80, which is the port used by
the Web server.
4.4.4 HTTP Headers
You can get the HTTP header information through the yiiwebHeaderCollection
returned by the yiiwebRequest::headers property. For example,
// $headers is an object of yiiwebHeaderCollection
$headers = Yii::$app->request->headers;
// returns the Accept header value
$accept = $headers->get(’Accept’);
if ($headers->has(’User-Agent’)) { // there is User-Agent header }
The request component also provides support for quickly accessing some
commonly used headers, including
• yiiwebRequest::userAgent: returns the value of the User-Agent
header.
150 CHAPTER 4. HANDLING REQUESTS
• yiiwebRequest::contentType: returns the value of the Content-Type
header which indicates the MIME type of the data in the request body.
• yiiwebRequest::acceptableContentTypes: returns the content MIME
types acceptable by users. The returned types ordered by the quality
score. Types with the highest scores will be returned first.
• yiiwebRequest::acceptableLanguages: returns the languages ac-
ceptable by users. The returned languages are ordered by their prefer-
ence level. The first element represents the most preferred language.
If your application supports multiple languages and you want to display
pages in the language that is the most preferred by the end user, you may use
the language negotiation method yiiwebRequest::getPreferredLanguage().
This method takes a list of languages supported by your application, com-
pares them with yiiwebRequest::acceptableLanguages, and returns the
most appropriate language.
Tip: You may also use the yiifiltersContentNegotiator
filter to dynamically determine what content type and language
should be used in the response. The filter implements the content
negotiation on top the properties and methods described above.
4.4.5 Client Information
You can get the host name and IP address of the client machine through
yiiwebRequest::userHost and yiiwebRequest::userIP, respectively.
For example,
$userHost = Yii::$app->request->userHost;
$userIP = Yii::$app->request->userIP;
4.5 Responses
When an application finishes handling a request, it generates a yiiweb
Response object and sends it to the end user. The response object contains
information such as the HTTP status code, HTTP headers and body. The
ultimate goal of Web application development is essentially to build such
response objects upon various requests.
In most cases you should mainly deal with the response application com-
ponent. However, Yii also allows you to create your own response objects
and send them to end users.
In this section, we will describe how to compose and send responses to
end users.
4.5. RESPONSES 151
4.5.1 Status Code
One of the first things you would do when building a response is to state
whether the request is successfully handled. This is done by setting the
yiiwebResponse::statusCode property which can take one of the valid
HTTP status codes1. For example, to indicate the request is successfully
handled, you may set the status code to be 200, like the following:
Yii::$app->response->statusCode = 200;
However, in most cases you do not need to explicitly set the status code.
This is because the default value of yiiwebResponse::statusCode is 200.
And if you want to indicate the request is unsuccessful, you may throw an
appropriate HTTP exception like the following:
throw new yiiwebNotFoundHttpException;
When the error handler catches an exception, it will extract the status
code from the exception and assign it to the response. For the yiiweb
NotFoundHttpException above, it is associated with the HTTP status 404.
The following HTTP exceptions are predefined in Yii:
• yiiwebBadRequestHttpException: status code 400.
• yiiwebConflictHttpException: status code 409.
• yiiwebForbiddenHttpException: status code 403.
• yiiwebGoneHttpException: status code 410.
• yiiwebMethodNotAllowedHttpException: status code 405.
• yiiwebNotAcceptableHttpException: status code 406.
• yiiwebNotFoundHttpException: status code 404.
• yiiwebServerErrorHttpException: status code 500.
• yiiwebTooManyRequestsHttpException: status code 429.
• yiiwebUnauthorizedHttpException: status code 401.
• yiiwebUnsupportedMediaTypeHttpException: status code 415.
If the exception that you want to throw is not among the above list, you may
create one by extending from yiiwebHttpException, or directly throw it
with a status code, for example,
throw new yiiwebHttpException(402);
1
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
152 CHAPTER 4. HANDLING REQUESTS
4.5.2 HTTP Headers
You can send HTTP headers by manipulating the yiiwebResponse::
headers in the response component. For example,
$headers = Yii::$app->response->headers;
// add a Pragma header. Existing Pragma headers will NOT be overwritten.
$headers->add(’Pragma’, ’no-cache’);
// set a Pragma header. Any existing Pragma headers will be discarded.
$headers->add(’Pragma’, ’no-cache’);
// remove Pragma header(s) and return the removed Pragma header values in
array
$values = $headers->remove(’Pragma’);
Info: Header names are case insensitive. And the newly re-
gistered headers are not sent to the user until the yiiwebResponse
::send() method is called.
4.5.3 Response Body
Most responses should have a body which gives the content that you want
to show to end users.
If you already have a formatted body string, you may assign it to the
yiiwebResponse::content property of the response. For example,
Yii::$app->request->content = ’hello world!’;
If you data needs to be formatted before sending to end users, you should
set both of the yiiwebResponse::format and yiiwebResponse::data
properties. The yiiwebResponse::format property specifies in which
format should the yiiwebResponse::data be formatted as. For example,
$response = Yii::$app->request;
$response->format = yiiwebResponse::FORMAT_JSON;
$response->data = [’message’ => ’hello world’];
Yii supports the following formats out of box, each implemented by a yii
webResponseFormatterInterface class. You can customize these format-
ters or add new ones by configuring the yiiwebResponse::formatters
property.
• yiiwebResponse::FORMAT_HTML: implemented by yiiwebHtmlResponseFormatter.
• yiiwebResponse::FORMAT_XML: implemented by yiiwebXmlResponseFormatter.
• yiiwebResponse::FORMAT_JSON: implemented by yiiwebJsonResponseFormatter.
• yiiwebResponse::FORMAT_JSONP: implemented by yiiwebJsonResponseFormatter.
4.5. RESPONSES 153
While response body can be set explicitly as shown above, in most cases you
may set it implicitly by the return value of action methods. A common use
case is like the following:
public function actionIndex()
{
return $this->render(’index’);
}
The index action above returns the rendering result of the index view. The
return value will be taken by the response component, formatted and then
sent to end users.
Because by default, the response format is as yiiwebResponse::FORMAT_HTML,
you should only return a string in an action method. If you want to use a
different response format, you should set it first before returning the data.
For example,
public function actionInfo()
{
Yii::$app->response->format = yiiwebResponse::FORMAT_JSON;
return [
’message’ => ’hello world’,
’code’ => 100,
];
}
As aforementioned, besides using the default response application component,
you can also create your own response objects and send them to end users.
You can do so by returning such an object in an action method, like the
following,
public function actionInfo()
{
return Yii::createObject([
’class’ => ’yiiwebResponse’,
’format’ => yiiwebResponse::FORMAT_JSON,
’data’ => [
’message’ => ’hello world’,
’code’ => 100,
],
]);
}
Note: If you are creating your own response objects, you will
not be able to take advantage of the configurations that you
set for the response component in the application configuration.
You can, however, use dependency injection to apply common
configuration to your new response objects.
154 CHAPTER 4. HANDLING REQUESTS
4.5.4 Browser Redirection
Browser redirection relies on sending a Location HTTP header. Because this
feature is commonly used, Yii provides some special supports for it.
You can redirect the user browser to a URL by calling the yiiweb
Response::redirect() method. The method sets the appropriate Location
header with the given URL and returns the response object itself. In
an action method, you can call its shortcut version yiiwebController
::redirect(). For example,
public function actionOld()
{
return $this->redirect(’http://example.com/new’, 301);
}
In the above code, the action method returns the result of the redirect()
method. As explained before, the response object returned by an action
method will be used as the response sending to end users.
In places other than an action method, you should call yiiwebResponse
::redirect() directly followed by a call to the yiiwebResponse::send()
method to ensure no extra content will be appended to the response.
Yii::$app->response->redirect(’http://example.com/new’, 301)->send();
Info: By default, the yiiwebResponse::redirect() method
sets the response status code to be 302 which instructs the browser
that the resource being requested is temporarily located in a dif-
ferent URI. You can pass in a status code 301 to tell the browser
that the resource has been permanently relocated.
When the current request is an AJAX request, sending a Location header will
not automatically cause the browser redirection. To solve this problem, the
yiiwebResponse::redirect() method sets an X-Redirect header with the
redirection URL as its value. On the client side you may write JavaScript
code to read this header value and redirect the browser accordingly.
Info: Yii comes with a yii.js JavaScript file which provides a set
of commonly used JavaScript utilities, including browser redirec-
tion based on the X-Redirect header. Therefore, if you are using
this JavaScript file (by registering the yiiwebYiiAsset asset
bundle), you do not need to write anything to support AJAX
redirection.
4.5.5 Sending Files
Like browser redirection, file sending is another feature that relies on specific
HTTP headers. Yii provides a set of methods to support various file sending
needs. They all have built-in support for HTTP range header.
4.5. RESPONSES 155
• yiiwebResponse::sendFile(): sends an existing file to client.
• yiiwebResponse::sendContentAsFile(): sends a text string as a
file to client.
• yiiwebResponse::sendStreamAsFile(): sends an existing file stream
as a file to client.
These methods have the same method signature with the response object
as the return value. If the file to be sent is very big, you should consider
using yiiwebResponse::sendStreamAsFile() because it is more memory
efficient. The following example shows how to send a file in a controller
action:
public function actionDownload()
{
return Yii::$app->response->sendFile(’path/to/file.txt’);
}
If you are calling the file sending method in places other than an action
method, you should also call the yiiwebResponse::send() method after-
wards to ensure no extra content will be appended to the response.
Yii::$app->response->sendFile(’path/to/file.txt’)->send();
Some Web servers have a special file sending support called X-Sendfile. The
idea is to redirect the request for a file to the Web server which will directly
serve the file. As a result, the Web application can terminate earlier while
the Web server is sending the file. To use this feature, you may call the
yiiwebResponse::xSendFile(). The following list summarizes how to
enable the X-Sendfile feature for some popular Web servers:
• Apache: X-Sendfile2
• Lighttpd v1.4: X-LIGHTTPD-send-file3
• Lighttpd v1.5: X-Sendfile4
• Nginx: X-Accel-Redirect5
• Cherokee: X-Sendfile and X-Accel-Redirect6
2
http://tn123.org/mod_xsendfile
3
http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file
4
http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file
5
http://wiki.nginx.org/XSendfile
6
http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile
156 CHAPTER 4. HANDLING REQUESTS
4.5.6 Sending Response
The content in a response is not sent to the user until the yiiwebResponse
::send() method is called. By default, this method will be called automat-
ically at the end of yiibaseApplication::run(). You can, however,
explicitly call this method to force sending out the response immediately.
The yiiwebResponse::send() method takes the following steps to
send out a response:
1. Trigger the yiiwebResponse::EVENT_BEFORE_SEND event.
2. Call yiiwebResponse::prepare() to format yiiwebResponse::
data into yiiwebResponse::content.
3. Trigger the yiiwebResponse::EVENT_AFTER_PREPARE event.
4. Call yiiwebResponse::sendHeaders() to send out the registered
HTTP headers.
5. Call yiiwebResponse::sendContent() to send out the response body
content.
6. Trigger the yiiwebResponse::EVENT_AFTER_SEND event.
After the yiiwebResponse::send() method is called once, any further
call to this method will be ignored. This means once the response is sent
out, you will not be able to append more content to it.
As you can see, the yiiwebResponse::send() method triggers several
useful events. By responding to these events, it is possible to adjust or
decorate the response.
4.5. RESPONSES 157
Error: not existing file: runtime-sessions-cookies.md
158 CHAPTER 4. HANDLING REQUESTS
4.6 URL Management
Note: This section is under development.
The concept of URL management in Yii is fairly simple. URL management is
based on the premise that the application uses internal routes and parameters
everywhere. The framework itself will then translate routes into URLs, and
vice versa, according to the URL manager’s configuration. This approach
allows you to change site-wide URLs merely by editing a single configuration
file, without ever touching the application code.
4.6.1 Internal routes
When implementing an application using Yii, you’ll deal with internal routes,
often referred to as routes and parameters. Each controller and controller
action has a corresponding internal route such as site/index or user/create.
In the first example, site is referred to as the controller ID while index is
referred to as the action ID. In the second example, user is the controller ID
and create is the action ID. If the controller belongs to a module, the internal
route is prefixed with the module ID. For example blog/post/index for a blog
module (with post being the controller ID and index being the action ID).
4.6.2 Creating URLs
The most important rule for creating URLs in your site is to always do
so using the URL manager. The URL manager is a built-in application
component named urlManager. This component is accessible from both web
and console applications via Yii::$app->urlManager. The component makes
available the two following URL creation methods:
• createUrl($params)
• createAbsoluteUrl($params, $schema = null)
The createUrl method creates an URL relative to the application root, such
as /index.php/site/index/. The createAbsoluteUrl method creates an URL
prefixed with the proper protocol and hostname: http://www.example.com/
index.php/site/index. The former is suitable for internal application URLs,
while the latter is used when you need to create URLs for external resources,
such as connecting to third party services, sending email, generating RSS
feeds etc.
Some examples:
echo Yii::$app->urlManager->createUrl([’site/page’, ’id’ => ’about’]);
// /index.php/site/page/id/about/
echo Yii::$app->urlManager->createUrl([’date-time/fast-forward’, ’id’ =>
105])
4.6. URL MANAGEMENT 159
// /index.php?r=date-time/fast-forward&id=105
echo Yii::$app->urlManager->createAbsoluteUrl(’blog/post/index’);
// http://www.example.com/index.php/blog/post/index/
The exact format of the URL depends on how the URL manager is con-
figured. The above examples may also output:
• /site/page/id/about/
• /index.php?r=site/page&id=about
• /index.php?r=date-time/fast-forward&id=105
• /index.php/date-time/fast-forward?id=105
• http://www.example.com/blog/post/index/
• http://www.example.com/index.php?r=blog/post/index
In order to simplify URL creation there is yiihelpersUrl helper. Assum-
ing we’re at /index.php?r=management/default/users&id=10 the following is how
Url helper works:
use yiihelpersUrl;
// currently active route
// /index.php?r=management/default/users
echo Url::to(’’);
// same controller, different action
// /index.php?r=management/default/page&id=contact
echo Url::toRoute([’page’, ’id’ => ’contact’]);
// same module, different controller and action
// /index.php?r=management/post/index
echo Url::toRoute(’post/index’);
// absolute route no matter what controller is making this call
// /index.php?r=site/index
echo Url::toRoute(’/site/index’);
// url for the case sensitive action ‘actionHiTech‘ of the current
controller
// /index.php?r=management/default/hi-tech
echo Url::toRoute(’hi-tech’);
// url for action the case sensitive controller, ‘DateTimeController::
actionFastForward‘
// /index.php?r=date-time/fast-forward&id=105
echo Url::toRoute([’/date-time/fast-forward’, ’id’ => 105]);
// get URL from alias
160 CHAPTER 4. HANDLING REQUESTS
// http://google.com/
Yii::setAlias(’@google’, ’http://google.com/’);
echo Url::to(’@google’);
// get home URL
// /index.php?r=site/index
echo Url::home();
Url::remember(); // save URL to be used later
Url::previous(); // get previously saved URL
Tip: In order to generate URL with a hashtag, for example /index
.php?r=site/page&id=100#title, you need to specify the parameter
named # using Url::to([’post/read’, ’id’ => 100, ’#’ => ’title’
]).
There’s also Url::canonical() method that allows you to generate canonical
URL7 for the currently executing action. The method ignores all action
parameters except ones passed via action arguments:
namespace appcontrollers;
use yiiwebController;
use yiihelpersUrl;
class CanonicalController extends Controller
{
public function actionTest($page)
{
echo Url::canonical();
}
}
When accessed as /index.php?r=canonical/test&page=hello&number=42 canonical
URL will be /index.php?r=canonical/test&page=hello.
4.6.3 Customizing URLs
By default, Yii uses a query string format for URLs, such as /index.php?r
=news/view&id=100. In order to make URLs human-friendly i.e., more read-
able, you need to configure the urlManager component in the application’s
configuration file. Enabling “pretty” URLs will convert the query string
format to a directory-based format: /index.php/news/view?id=100. Disabling
the showScriptName parameter means that index.php will not be part of the
URLs. Here’s the relevant part of the application’s configuration file:
<?php
return [
// ...
7
https://en.wikipedia.org/wiki/Canonical_link_element
4.6. URL MANAGEMENT 161
’components’ => [
’urlManager’ => [
’enablePrettyUrl’ => true,
’showScriptName’ => false,
],
],
];
Note that this configuration will only work if the web server has been properly
configured for Yii, see installation.
Named parameters
A rule can be associated with a few GET parameters. These GET parameters
appear in the rule’s pattern as special tokens in the following format:
<ParameterName:ParameterPattern>
ParameterName is a name of a GET parameter, and the optional ParameterPattern
is the regular expression that should be used to match the value of the
GET parameter. In case ParameterPattern is omitted, it means the parameter
should match any characters except /. When creating a URL, these para-
meter tokens will be replaced with the corresponding parameter values; when
parsing a URL, the corresponding GET parameters will be populated with
the parsed results.
Let’s use some examples to explain how URL rules work. We assume
that our rule set consists of three rules:
[
’posts’=>’post/list’,
’post/<id:d+>’=>’post/read’,
’post/<year:d{4}>/<title>’=>’post/read’,
]
• Calling Url::toRoute(’post/list’) generates /index.php/posts. The first
rule is applied.
• Calling Url::toRoute([’post/read’, ’id’ => 100]) generates /index.php/
post/100. The second rule is applied.
• Calling Url::toRoute([’post/read’, ’year’ => 2008, ’title’ => ’a sample
post’]) generates /index.php/post/2008/a%20sample%20post. The third
rule is applied.
• Calling Url::toRoute(’post/read’) generates /index.php/post/read. None
of the rules is applied, convention is used instead.
In summary, when using createUrl to generate a URL, the route and the GET
parameters passed to the method are used to decide which URL rule to be
applied. If every parameter associated with a rule can be found in the GET
162 CHAPTER 4. HANDLING REQUESTS
parameters passed to createUrl, and if the route of the rule also matches the
route parameter, the rule will be used to generate the URL.
If the GET parameters passed to Url::toRoute are more than those required
by a rule, the additional parameters will appear in the query string. For
example, if we call Url::toRoute([’post/read’, ’id’ => 100, ’year’ => 2008]),
we will obtain /index.php/post/100?year=2008.
As we mentioned earlier, the other purpose of URL rules is to parse the
requesting URLs. Naturally, this is an inverse process of URL creation. For
example, when a user requests for /index.php/post/100, the second rule in the
above example will apply, which resolves in the route post/read and the GET
parameter [’id’ => 100] (accessible via Yii::$app->request->get(’id’)).
Parameterizing Routes
We may reference named parameters in the route part of a rule. This allows
a rule to be applied to multiple routes based on matching criteria. It may
also help reduce the number of rules needed for an application, and thus
improve the overall performance.
We use the following example rules to illustrate how to parameterize
routes with named parameters:
[
’<controller:(post|comment)>/<id:d+>/<action:(create|update|delete)>’
=> ’<controller>/<action>’,
’<controller:(post|comment)>/<id:d+>’ => ’<controller>/read’,
’<controller:(post|comment)>s’ => ’<controller>/list’,
]
In the above example, we use two named parameters in the route part of
the rules: controller and action. The former matches a controller ID to be
either post or comment, while the latter matches an action ID to be create,
update or delete. You may name the parameters differently as long as they
do not conflict with GET parameters that may appear in URLs.
Using the above rules, the URL /index.php/post/123/create will be parsed
as the route post/create with GET parameter id=123. Given the route comment
/list and GET parameter page=2, we can create a URL /index.php/comments?
page=2.
Parameterizing hostnames
It is also possible to include hostnames in the rules for parsing and creating
URLs. One may extract part of the hostname to be a GET parameter. This
is especially useful for handling subdomains. For example, the URL http://
admin.example.com/en/profile may be parsed into GET parameters user=admin
and lang=en. On the other hand, rules with hostname may also be used to
create URLs with parameterized hostnames.
4.6. URL MANAGEMENT 163
In order to use parameterized hostnames, simply declare URL rules with
host info, e.g.:
[
’http://<user:w+>.example.com/<lang:w+>/profile’ => ’user/profile’,
]
In the above example the first segment of the hostname is treated as the
user parameter while the first segment of the path info is treated as the lang
parameter. The rule corresponds to the user/profile route.
Note that yiiwebUrlManager::showScriptName will not take effect
when a URL is being created using a rule with a parameterized hostname.
Also note that any rule with a parameterized hostname should NOT
contain the subfolder if the application is under a subfolder of the Web root.
For example, if the application is under http://www.example.com/sandbox/blog,
then we should still use the same URL rule as described above without the
subfolder sandbox/blog.
Faking URL Suffix
<?php
return [
// ...
’components’ => [
’urlManager’ => [
’suffix’ => ’.html’,
],
],
];
Handling REST requests
TBD: - RESTful routing: yiifiltersVerbFilter, yiiwebUrlManager
::$rules - Json API: - response: yiiwebResponse::format - request:
yiiwebRequest::$parsers, yiiwebJsonParser
4.6.4 URL parsing
Complimentary to creating URLs Yii also handles transforming custom URLs
back into internal routes and parameters.
Strict URL parsing
By default if there’s no custom rule for a URL and the URL matches the
default format such as /site/page, Yii tries to run the corresponding con-
troller’s action. This behavior can be disabled so if there’s no custom rule
match, a 404 not found error will be produced immediately.
164 CHAPTER 4. HANDLING REQUESTS
<?php
return [
// ...
’components’ => [
’urlManager’ => [
’enableStrictParsing’ => true,
],
],
];
4.6.5 Creating your own rule classes
yiiwebUrlRule class is used for both parsing URL into parameters and
creating URL based on parameters. Despite the fact that default implement-
ation is flexible enough for the majority of projects, there are situations when
using your own rule class is the best choice. For example, in a car dealer web-
site, we may want to support the URL format like /Manufacturer/Model, where
Manufacturer and Model must both match some data in a database table. The
default rule class will not work because it mostly relies on statically declared
regular expressions which have no database knowledge.
We can write a new URL rule class by extending from yiiwebUrlRule
and use it in one or multiple URL rules. Using the above car dealer website
as an example, we may declare the following URL rules in application config:
// ...
’components’ => [
’urlManager’ => [
’rules’ => [
’<action:(login|logout|about)>’ => ’site/<action>’,
// ...
[’class’ => ’appcomponentsCarUrlRule’, ’connectionID’ => ’db’,
/* ... */],
],
],
],
In the above, we use the custom URL rule class CarUrlRule to handle the
URL format /Manufacturer/Model. The class can be written like the following:
namespace appcomponents;
use yiiwebUrlRule;
class CarUrlRule extends UrlRule
{
public $connectionID = ’db’;
public function init()
{
4.7. ERROR HANDLING 165
if ($this->name === null) {
$this->name = __CLASS__;
}
}
public function createUrl($manager, $route, $params)
{
if ($route === ’car/index’) {
if (isset($params[’manufacturer’], $params[’model’])) {
return $params[’manufacturer’] . ’/’ . $params[’model’];
} elseif (isset($params[’manufacturer’])) {
return $params[’manufacturer’];
}
}
return false; // this rule does not apply
}
public function parseRequest($manager, $request)
{
$pathInfo = $request->getPathInfo();
if (preg_match(’%^(w+)(/(w+))?$%’, $pathInfo, $matches)) {
// check $matches[1] and $matches[3] to see
// if they match a manufacturer and a model in the database
// If so, set $params[’manufacturer’] and/or $params[’model’]
// and return [’car/index’, $params]
}
return false; // this rule does not apply
}
}
Besides the above usage, custom URL rule classes can also be implemented
for many other purposes. For example, we can write a rule class to log the
URL parsing and creation requests. This may be useful during development
stage. We can also write a rule class to display a special 404 error page in
case all other URL rules fail to resolve the current request. Note that in this
case, the rule of this special class must be declared as the last rule.
4.7 Error Handling
Note: This section is under development.
Error handling in Yii is different than handling errors in plain PHP. First of
all, Yii will convert all non-fatal errors to exceptions:
use yiibaseErrorException;
use Yii;
try {
10/0;
} catch (ErrorException $e) {
Yii::warning("Tried dividing by zero.");
}
166 CHAPTER 4. HANDLING REQUESTS
// execution may continue
As demonstrated above you may handle errors using try-catch.
Second, even fatal errors in Yii are rendered in a nice way. This means
that in debugging mode, you can trace the causes of fatal errors in order to
more quickly identify the cause of the problem.
4.7.1 Rendering errors in a dedicated controller action
The default Yii error page is great when developing a site, and is acceptable
for production sites if YII_DEBUG is turned off in your bootstrap index.php
file. But you may want to customize the default error page to make it more
suitable for your project.
The easiest way to create a custom error page it is to use a dedicated
controller action for error rendering. First, you’ll need to configure the
errorHandler component in the application’s configuration:
// ...
’components’ => [
// ...
’errorHandler’ => [
’errorAction’ => ’site/error’,
],
]
With that configuration in place, whenever an error occurs, Yii will execute
the error-action of the site-controller. That action should look for an excep-
tion and, if present, render the proper view file, passing along the exception:
public function actionError()
{
$exception = Yii::$app->errorHandler->exception;
if ($exception !== null) {
return $this->render(’error’, [’exception’ => $exception]);
}
}
Next, you would create the views/site/error.php file, which would make use
of the exception. The exception object has the following properties:
• statusCode: the HTTP status code (e.g. 403, 500). Available for yii
webHttpException only.
• code: the code of the exception.
• message: the error message.
• file: the name of the PHP script file where the error occurs.
• line: the line number of the code where the error occurs.
• trace: the call stack of the error.
4.8. LOGGING 167
4.7.2 Rendering errors without a dedicated controller action
Instead of creating a dedicated action within the Site controller, you could
just indicate to Yii what class should be used to handle errors:
public function actions()
{
return [
’error’ => [
’class’ => ’yiiwebErrorAction’,
],
];
}
After associating the class with the error as in the above, define the views/
site/error.php file, which will automatically be used. The view will be passed
three variables:
• $name: the error name
• $message: the error message
• $exception: the exception being handled
The $exception object will have the same properties as outlined above.
4.8 Logging
Note: This section is under development.
Yii provides flexible and extensible logger that is able to handle messages
according to severity level or their type. You may filter messages by multiple
criteria and forward them to files, email, debugger etc.
4.8.1 Logging basics
Basic logging is as simple as calling one method:
Yii::info(’Hello, I am a test log message’);
You can log simple strings as well as more complex data structures such as
arrays or objects. When logging data that is not a string the defaulf yii log
targets will serialize the value using yiihelpersVardumper::export().
Message category
Additionally to the message itself message category could be specified in
order to allow filtering such messages and handing these differently. Message
category is passed as a second argument of logging methods and is application
by default.
168 CHAPTER 4. HANDLING REQUESTS
Severity levels
There are multiple severity levels and corresponding methods available:
• Yii::trace used maily for development purpose to indicate workflow
of some code. Note that it only works in development mode when
YII_DEBUG is set to true.
• Yii::error used when there’s unrecoverable error.
• Yii::warning used when an error occurred but execution can be con-
tinued.
• Yii::info used to keep record of important events such as adminis-
trator logins.
4.8.2 Log targets
When one of the logging methods is called, message is passed to yiilog
Logger component accessible as Yii::getLogger(). Logger accumulates mes-
sages in memory and then when there are enough messages or when the
current request finishes, sends them to different log targets, such as file or
email.
You may configure the targets in application configuration, like the fol-
lowing:
[
’bootstrap’ => [’log’], // ensure logger gets loaded before application
starts
’components’ => [
’log’ => [
’targets’ => [
’file’ => [
’class’ => ’yiilogFileTarget’,
’levels’ => [’trace’, ’info’],
’categories’ => [’yii*’],
],
’email’ => [
’class’ => ’yiilogEmailTarget’,
’levels’ => [’error’, ’warning’],
’message’ => [
’to’ => [’admin@example.com’, ’developer@example.com
’],
’subject’ => ’New example.com log message’,
],
],
],
],
],
]
4.8. LOGGING 169
In the config above we are defining two log targets: yiilogFileTarget
and yiilogEmailTarget. In both cases we are filtering messages handles
by these targets by severity. In case of file target we’re additionally filter by
category. yii* means all categories starting with yii.
Each log target can have a name and can be referenced via the yiilog
Logger::targets property as follows:
Yii::$app->log->targets[’file’]->enabled = false;
When the application ends or yiilogLogger::flushInterval is reached,
Logger will call yiilogLogger::flush() to send logged messages to dif-
ferent log targets, such as file, email, web.
Note: In the above configuration we added the log component
to the list of bootstrap components that get initialized when the
application is initialized to ensure logging is enabled from the
start.
4.8.3 Profiling
Performance profiling is a special type of message logging that can be used
to measure the time needed for the specified code blocks to execute and find
out what the performance bottleneck is.
To use it we need to identify which code blocks need to be profiled.
Then we mark the beginning and the end of each code block by inserting the
following methods:
Yii::beginProfile(’myBenchmark’);
...code block being profiled...
Yii::endProfile(’myBenchmark’);
where myBenchmark uniquely identifies the code block.
Note, code blocks need to be nested properly such as
Yii::beginProfile(’block1’);
// some code to be profiled
Yii::beginProfile(’block2’);
// some other code to be profiled
Yii::endProfile(’block2’);
Yii::endProfile(’block1’);
Profiling results could be displayed in debugger.
170 CHAPTER 4. HANDLING REQUESTS
Chapter 5
Key Concepts
5.1 Components
Components are the main building blocks of Yii applications. Components
are instances of yiibaseComponent, or an extended class. The three main
features that components provide to other classes are:
• Properties
• Events
• Behaviors,
Separately and combined, these features make Yii classes much more custom-
izable and easier to use. For example, the included yiijuiDatePicker, a
user interface component, can be used in a view to generate an interactive
date picker:
use yiijuiDatePicker;
echo DatePicker::widget([
’language’ => ’ru’,
’name’ => ’country’,
’clientOptions’ => [
’dateFormat’ => ’yy-mm-dd’,
],
]);
The widget’s properties are easily writable because the class extends yii
baseComponent.
While components are very powerful, they are a bit heavier than normal
objects, due to the fact that it takes extra memory and CPU time to support
event and behavior functionality in particular. If your components do not
need these two features, you may consider extending your component class
from yiibaseObject instead of yiibaseComponent. Doing so will make
171
172 CHAPTER 5. KEY CONCEPTS
your components as efficient as normal PHP objects, but with added support
for properties.
When extending your class from yiibaseComponent or yiibaseObject,
it is recommended that you follow these conventions:
• If you override the constructor, specify a $config parameter as the con-
structor’s last parameter, and then pass this parameter to the parent
constructor.
• Always call the parent constructor at the end of your overriding con-
structor.
• If you override the yiibaseObject::init() method, make sure you
call the parent implementation of init at the beginning of your init
method.
For example:
namespace yiicomponentsMyClass;
use yiibaseObject;
class MyClass extends Object
{
public $prop1;
public $prop2;
public function __construct($param1, $param2, $config = [])
{
// ... initialization before configuration is applied
parent::__construct($config);
}
public function init()
{
parent::init();
// ... initialization after configuration is applied
}
}
Following these guidelines will make your components configurable when
they are created. For example:
$component = new MyClass(1, 2, [’prop1’ => 3, ’prop2’ => 4]);
// alternatively
$component = Yii::createObject([
’class’ => MyClass::className(),
’prop1’ => 3,
’prop2’ => 4,
], [1, 2]);
5.2. PROPERTIES 173
Info: While the approach of calling Yii::createObject() looks
more complicated, it is more powerful because it is implemented
on top of a dependency injection container.
The yiibaseObject class enforces the following object lifecycle:
1. Pre-initialization within the constructor. You can set default property
values here.
2. Object configuration via $config. The configuration may overwrite the
default values set within the constructor.
3. Post-initialization within yiibaseObject::init(). You may over-
ride this method to perform sanity checks and normalization of the
properties.
4. Object method calls.
The first three steps all happen within the object’s constructor. This means
that once you get a class instance (i.e., an object), that object has already
been initialized to a proper, reliable state.
5.2 Properties
In PHP, class member variables are also called properties. These variables
are part of the class definition, and are used to represent the state of a class
instance (i.e., to differentiate one instance of the class from another). In
practice, you may often want to handle the reading or writing of properties
in special ways. For example, you may want to always trim a string when
it is being assigned to a label property. You could use the following code to
achieve this task:
$object->label = trim($label);
The drawback of the above code is that you would have to call trim() every-
where in your code where you might set the label property. If, in the future,
the label property gets a new requirement, such as the first letter must be
captialized, you would again have to modify every bit of code that assigns a
value to label. The repetition of code leads to bugs, and is a practice you
want to avoid as much as possible.
To solve this problem, Yii introduces a base class called yiibaseObject
that supports defining properties based on getter and setter class methods.
If a class needs that functionality, it should extend from yiibaseObject,
or from a child class.
174 CHAPTER 5. KEY CONCEPTS
Info: Nearly every core class in the Yii framework extends from
yiibaseObject or a child class. This means that whenever
you see a getter or setter in a core class, you can use it like a
property.
A getter method is a method whose name starts with the word get; a setter
method starts with set. The name after the get or set prefix defines the name
of a property. For example, a getter getLabel() and/or a setter setLabel()
defines a property named label, as shown in the following code:
namespace appcomponents;
use yiibaseObject;
class Foo extend Object
{
private $_label;
public function getLabel()
{
return $this->_label;
}
public function setLabel($value)
{
$this->_label = trim($value);
}
}
(To be clear, the getter and setter methods create the property label, which
in this case internally refer to a private attributed named _label.)
Properties defined by getters and setters can be used like class member
variables. The main difference is that when such a property is being read,
the corresponding getter method will be called; when the property is be-
ing assigned a value, the corresponding setter method will be called. For
example:
// equivalent to $label = $object->getLabel();
$label = $object->label;
// equivalent to $object->setLabel(’abc’);
$object->label = ’abc’;
A property defined by a getter without a setter is read only. Trying to assign
a value to such a property will cause an yiibaseInvalidCallException.
Similarly, a property defined by a setter without a getter is write only, and
trying to read such a property will also cause an exception. It is not common
to have write-only properties.
There are several special rules for, and limitations on, the properties
defined via getters and setters:
5.3. EVENTS 175
• The names of such properties are case-insensitive. For example, $object
->label and $object->Label are the same. This is because method names
in PHP are case-insensitive.
• If the name of such a property is the same as a class member variable,
the latter will take precedence. For example, if the above Foo class has
a member variable label, then the assignment $object->label = ’abc’
will affect the member variable ‘label’; that line would not call the
setLabel() setter method.
• These properties do not support visibility. It makes no difference for
the visibility of a property if the defining getter or setter method is
public, protected or private.
• The properties can only be defined by non-static getters and/or setters.
Static methods will not be treated in this same manner.
Returning back to the problem described at the beginning of this guide,
instead of calling trim() everywhere a label value is assigned, trim() now only
needs to be invoked within the setter setLabel(). And if a new requirement
comes that requires the label be initially capitalized, the setLabel() method
can quickly be modified without touching any other code. The one change
will universally affect every assignment to label.
5.3 Events
Events allow you to inject custom code into existing code at certain execution
points. You can attach custom code to an event so that when the event is
triggered, the code gets executed automatically. For example, a mailer object
may trigger a messageSent event when it successfully sends a message. If you
want to keep track of the messages that are successfully sent, you could then
simply attach the tracking code to the messageSent event.
Yii introduces a base class called yiibaseComponent to support events.
If a class needs to trigger events, it should extend from yiibaseComponent,
or from a child class.
5.3.1 Event Handlers
An event handler is a PHP callback1 that gets executed when the event it is
attached to is triggered. You can use any of the following callbacks:
• a global PHP function specified as a string (without parentheses), e.g.,
’trim’;
1
http://www.php.net/manual/en/language.types.callable.php
176 CHAPTER 5. KEY CONCEPTS
• an object method specified as an array of an object and a method name
as a string (without parenthess), e.g., [$object, ’methodName’];
• a static class method specified as an array of a class name and a method
name as a string (without parentheses), e.g., [$class, ’methodName’];
• an anonymous function, e.g., function ($event) { ... }.
The signature of an event handler is:
function ($event) {
// $event is an object of yiibaseEvent or a child class
}
Through the $event parameter, an event handler may get the following in-
formation about the event that occurred:
• yiibaseEvent::name
• yiibaseEvent::sender: the object whose trigger() method was
called
• yiibaseEvent::data: the data that is provided when attaching the
event handler (to be explained next)
5.3.2 Attaching Event Handlers
You can attach a handler to an event by calling the yiibaseComponent::
on() method. For example:
$foo = new Foo;
// this handler is a global function
$foo->on(Foo::EVENT_HELLO, ’function_name’);
// this handler is an object method
$foo->on(Foo::EVENT_HELLO, [$object, ’methodName’]);
// this handler is a static class method
$foo->on(Foo::EVENT_HELLO, [’appcomponentsBar’, ’methodName’]);
// this handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
// event handling logic
});
You may also attach event handlers through configurations. For more details,
please refer to the Configurations section.
When attaching an event handler, you may provide additional data as
the third parameter to yiibaseComponent::on(). The data will be made
available to the handler when the event is triggered and the handler is called.
For example:
5.3. EVENTS 177
// The following code will display "abc" when the event is triggered
// because $event->data contains the data passed as the 3rd argument to "on"
$foo->on(Foo::EVENT_HELLO, ’function_name’, ’abc’);
function function_name($event) {
echo $event->data;
}
5.3.3 Event Handler Order
You may attach one or more handlers to a single event. When an event is
triggered, the attached handlers will be called in the order that they were
attached to the event. If a handler needs to stop the invocation of the
handlers that follow it, it may set the yiibaseEvent::handled property
of the $event parameter to be true:
$foo->on(Foo::EVENT_HELLO, function ($event) {
$event->handled = true;
});
By default, a newly attached handler is appended to the existing handler
queue for the event. As a result, the handler will be called in the last place
when the event is triggered. To insert the new handler at the start of the
handler queue so that the handler gets called first, you may call yiibase
Component::on(), passing false for the fourth parameter $append:
$foo->on(Foo::EVENT_HELLO, function ($event) {
// ...
}, $data, false);
5.3.4 Triggering Events
Events are triggered by calling the yiibaseComponent::trigger() method.
The method requires an event name, and optionally an event object that de-
scribes the parameters to be passed to the event handlers. For example:
namespace appcomponents;
use yiibaseComponent;
use yiibaseEvent;
class Foo extends Component
{
const EVENT_HELLO = ’hello’;
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}
With the above code, any calls to bar() will trigger an event named hello.
178 CHAPTER 5. KEY CONCEPTS
Tip: It is recommended to use class constants to represent event
names. In the above example, the constant EVENT_HELLO repres-
ents the hello event. This approach has three benefits. First,
it prevents typos. Second, it can make events recognizable for
IDE auto-completion support. Third, you can tell what events
are supported in a class by simply checking its constant declara-
tions.
Sometimes when triggering an event you may want to pass along additional
information to the event handlers. For example, a mailer may want pass
the message information to the handlers of the messageSent event so that the
handlers can know the particulars of the sent messages. To do so, you can
provide an event object as the second parameter to the yiibaseComponent
::trigger() method. The event object must be an instance of the yiibase
Event class, or of a child class. For example:
namespace appcomponents;
use yiibaseComponent;
use yiibaseEvent;
class MessageEvent extends Event
{
public $message;
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = ’messageSent’;
public function send($message)
{
// ...sending $message...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
When the yiibaseComponent::trigger() method is called, it will call all
handlers attached to the named event.
5.3.5 Detaching Event Handlers
To detach a handler from an event, call the yiibaseComponent::off()
method. For example:
// the handler is a global function
$foo->off(Foo::EVENT_HELLO, ’function_name’);
5.3. EVENTS 179
// the handler is an object method
$foo->off(Foo::EVENT_HELLO, [$object, ’methodName’]);
// the handler is a static class method
$foo->off(Foo::EVENT_HELLO, [’appcomponentsBar’, ’methodName’]);
// the handler is an anonymous function
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
Note that in general you should not try to detach an anonymous function
unless you store it somewhere when it is attached to the event. In the above
example, it is assumed that the anonymous function is stored as a variable
$anonymousFunction.
To detach ALL handlers from an event, simply call yiibaseComponent
::off() without the second parameter:
$foo->off(Foo::EVENT_HELLO);
5.3.6 Class-Level Event Handlers
The above subsections described how to attach a handler to an event on an
instance level. Sometimes, you may want to respond to an event triggered
by every instance of a class instead of only by a specific instance. Instead
of attaching an event handler to every instance, you may attach the handler
on the class level by calling the static method yiibaseEvent::on().
For example, an Active Record object will trigger an yiibaseActiveRecord
::EVENT_AFTER_INSERT event whenever it inserts a new record into the data-
base. In order to track insertions done by every Active Record object, you
may use the following code:
use Yii;
use yiibaseEvent;
use yiidbActiveRecord;
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT,
function ($event) {
Yii::trace(get_class($event->sender) . ’ is inserted’);
});
The event handler will be invoked whenever an instance of yiibaseActiveRecord,
or one of its child classes, triggers the yiibaseActiveRecord::EVENT_AFTER_INSERT
event. In the handler, you can get the object that triggered the event through
$event->sender.
When an object triggers an event, it will first call instance-level handlers,
followed by the class-level handlers.
You may trigger a class-level event by calling the static method yiibase
Event::trigger(). A class-level event is not associated with a particular
object. As a result, it will cause the invocation of class-level event handlers
only. For example:
180 CHAPTER 5. KEY CONCEPTS
use yiibaseEvent;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
echo $event->sender; // displays "appmodelsFoo"
});
Event::trigger(Foo::className(), Foo::EVENT_HELLO);
Note that, in this case, $event->sender refers to the name of the class trigger-
ing the event instead of an object instance.
Note: Because a class-level handler will respond to an event
triggered by any instance of that class, or any child classes, you
should use it carefully, especially if the class is a low-level base
class, such as yiibaseObject.
To detach a class-level event handler, call yiibaseEvent::off(). For
example:
// detach $handler
Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);
// detach all handlers of Foo::EVENT_HELLO
Event::off(Foo::className(), Foo::EVENT_HELLO);
5.3.7 Global Events
Yii supports a so-called global event, which is actually a trick based on the
event mechanism described above. The global event requires a globally ac-
cessible Singleton, such as the application instance itself.
To create the global evant, an event sender calls the Singleton’s trigger()
method to trigger the event, instead of calling the sender’s own trigger()
method. Similarly, the event handlers are attached to the event on the
Singleton. For example:
use Yii;
use yiibaseEvent;
use appcomponentsFoo;
Yii::$app->on(’bar’, function ($event) {
echo get_class($event->sender); // displays "appcomponentsFoo"
});
Yii::$app->trigger(’bar’, new Event([’sender’ => new Foo]));
A benefit of using global events is that you do not need an object when at-
taching a handler to the event which will be triggered by the object. Instead,
the handler attachment and the event triggering are both done through the
Singleton (e.g. the application instance).
5.4. BEHAVIORS 181
However, because the namespace of the global events is shared by all
parties, you should name the global events wisely, such as introducing some
sort of namespace (e.g. “frontend.mail.sent”, “backend.mail.sent”).
5.4 Behaviors
Behaviors are instances of yiibaseBehavior, or of a child class. Beha-
viors, also known as mixins2, allow you to enhance the functionality of an
existing yiibaseComponent class without needing to change the class’s
inheritance. Attaching a behavior to a component “injects” the behavior’s
methods and properties into the component, making those methods and
properties accessible as if they were defined in the component class itself.
Moreover, a behavior can respond to the events triggered by the component,
which allows behaviors to also customize the normal code execution of the
component.
5.4.1 Defining Behaviors
To define a behavior, create a class that extends yiibaseBehavior, or
extends a child class. For example:
namespace appcomponents;
use yiibaseBehavior;
class MyBehavior extends Behavior
{
public $prop1;
private $_prop2;
public function getProp2()
{
return $this->_prop2;
}
public function setProp2($value)
{
$this->_prop2 = $value;
}
public function foo()
{
// ...
}
}
2
http://en.wikipedia.org/wiki/Mixin
182 CHAPTER 5. KEY CONCEPTS
The above code defines the behavior class appcomponentsMyBehavior, with
two properties– prop1 and prop2–and one method foo(). Note that property
prop2 is defined via the getter getProp2() and the setter setProp2(). This is the
case because yiibaseBehavior extends yiibaseObject, and therefore
supports defining properties via getters and setters.
Because this class is a behavior, when it is attached to a component,
that component will then also have the the prop1 and prop2 properties and
the foo() method.
Tip: Within a behavior, you can access the component that the
behavior is attached to through the yiibaseBehavior::owner
property.
5.4.2 Handling Component Events
If a behavior needs to respond to the events triggered by the component it is
attached to, it should override the yiibaseBehavior::events() method.
For example:
namespace appcomponents;
use yiidbActiveRecord;
use yiibaseBehavior;
class MyBehavior extends Behavior
{
// ...
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => ’beforeValidate’,
];
}
public function beforeValidate($event)
{
// ...
}
}
The yiibaseBehavior::events() method should return a list of events
and their corresponding handlers. The above example declares that the yii
dbActiveRecord::EVENT_BEFORE_VALIDATE event exists and defines its
handler, beforeValidate(). When specifying an event handler, you may use
one of the following formats:
• a string that refers to the name of a method of the behavior class, like
the example above
5.4. BEHAVIORS 183
• an array of an object or class name, and a method name as a string
(without parentheses), e.g., [$object, ’methodName’];
• an anonymous function
The signature of an event handler should be as follows, where $event refers
to the event parameter. Please refer to the Events section for more details
about events.
function ($event) {
}
5.4.3 Attaching Behaviors
You can attach a behavior to a yiibaseComponent either statically or
dynamically. The former is more common in practice.
To attach a behavior statically, override the yiibaseComponent::behaviors()
method of the component class to which the behavior is being attached. The
yiibaseComponent::behaviors() method should return a list of behavior
configurations. Each behavior configuration can be either a behavior class
name or a configuration array:
namespace appmodels;
use yiidbActiveRecord;
use appcomponentsMyBehavior;
class User extends ActiveRecord
{
public function behaviors()
{
return [
// anonymous behavior, behavior class name only
MyBehavior::className(),
// named behavior, behavior class name only
’myBehavior2’ => MyBehavior::className(),
// anonymous behavior, configuration array
[
’class’ => MyBehavior::className(),
’prop1’ => ’value1’,
’prop2’ => ’value2’,
],
// named behavior, configuration array
’myBehavior4’ => [
’class’ => MyBehavior::className(),
’prop1’ => ’value1’,
’prop2’ => ’value2’,
]
184 CHAPTER 5. KEY CONCEPTS
];
}
}
You may associate a name with a behavior by specifying the array key cor-
responding to the behavior configuration. In this case, the behavior is called
a named behavior. In the above example, there are two named behaviors:
myBehavior2 and myBehavior4. If a behavior is not associated with a name, it
is called an anonymous behavior.
To attach a behavior dynamically, call the yiibaseComponent::attachBehavior()
method of the component to which the behavior is being attached:
use appcomponentsMyBehavior;
// attach a behavior object
$component->attachBehavior(’myBehavior1’, new MyBehavior);
// attach a behavior class
$component->attachBehavior(’myBehavior2’, MyBehavior::className());
// attach a configuration array
$component->attachBehavior(’myBehavior3’, [
’class’ => MyBehavior::className(),
’prop1’ => ’value1’,
’prop2’ => ’value2’,
]);
You may attach multiple behaviors at once using the yiibaseComponent
::attachBehaviors() method:
$component->attachBehaviors([
’myBehavior1’ => new MyBehavior, // a named behavior
MyBehavior::className(), // an anonymous behavior
]);
You may also attach behaviors through configurations like the following:
[
’as myBehavior2’ => MyBehavior::className(),
’as myBehavior3’ => [
’class’ => MyBehavior::className(),
’prop1’ => ’value1’,
’prop2’ => ’value2’,
],
]
For more details, please refer to the Configurations section.
5.4.4 Using Behaviors
To use a behavior, first attach it to a yiibaseComponent per the in-
structions above. Once a behavior is attached to a component, its usage
is straightforward.
5.4. BEHAVIORS 185
You can access a public member variable or a property defined by a getter
and/or a setter of the behavior through the component it is attached to:
// "prop1" is a property defined in the behavior class
echo $component->prop1;
$component->prop1 = $value;
You can also call a public method of the behavior similarly:
// foo() is a public method defined in the behavior class
$component->foo();
As you can see, although $component does not define prop1 and foo(), they can
be used as if they are part of the component definition due to the attached
behavior.
If two behaviors define the same property or method and they are both
attached to the same component, the behavior that is attached to the com-
ponent first will take precedence when the property or method is accessed.
A behavior may be associated with a name when it is attached to a
component. If this is the case, you may access the behavior object using the
name:
$behavior = $component->getBehavior(’myBehavior’);
You may also get all behaviors attached to a component:
$behaviors = $component->getBehaviors();
5.4.5 Detaching Behaviors
To detach a behavior, call yiibaseComponent::detachBehavior() with
the name associated with the behavior:
$component->detachBehavior(’myBehavior1’);
You may also detach all behaviors:
$component->detachBehaviors();
5.4.6 Using TimestampBehavior
To wrap up, let’s take a look at yiibehaviorsTimestampBehavior. This
behavior supports automatically updating the timestamp attributes of an
yiidbActiveRecord model anytime the model is saved (e.g., on insert or
update).
First, attach this behavior to the yiidbActiveRecord class that you
plan to use:
namespace appmodelsUser;
use yiidbActiveRecord;
use yiibehaviorsTimestampBehavior;
186 CHAPTER 5. KEY CONCEPTS
class User extends ActiveRecord
{
// ...
public function behaviors()
{
return [
[
’class’ => TimestampBehavior::className(),
’attributes’ => [
ActiveRecord::EVENT_BEFORE_INSERT => [’created_at’, ’
updated_at’],
ActiveRecord::EVENT_BEFORE_UPDATE => [’updated_at’],
],
],
];
}
}
The behavior configuration above specifies that when the record is being:
• inserted, the behavior should assign the current timestamp to the
created_at and updated_at attributes
• updated, the behavior should assign the current timestamp to the
updated_at attribute
With that code in place, if you have a User object and try to save it, you will
find its created_at and updated_at are automatically filled with the current
timestamp:
$user = new User;
$user->email = ’test@example.com’;
$user->save();
echo $user->created_at; // shows the current timestamp
The yiibehaviorsTimestampBehavior also offers a useful method yii
behaviorsTimestampBehavior::touch(), which will assign the current
timestamp to a specified attribute and save it to the database:
$user->touch(’login_time’);
5.4.7 Comparing Behaviors with Traits
While behaviors are similar to traits3 in that they both “inject” their prop-
erties and methods to the primary class, they differ in many aspects. As
explained below, they both have pros and cons. They are more like comple-
ments to each other rather than alternatives.
3
http://www.php.net/traits
5.5. CONFIGURATIONS 187
Reasons to Use Behaviors
Behavior classes, like normal classes, support inheritance. Traits, on the
other hand, can be considered as language-supported copy and paste. They
do not support inheritance.
Behaviors can be attached and detached to a component dynamically
without requiring modification of the component class. To use a trait, you
must modify the class using it.
Behaviors are configurable while traits are not.
Behaviors can customize the code execution of a component by respond-
ing to its events.
When there can be name conflicts among different behaviors attached to
the same component, the conflicts are automatically resolved by prioritizing
the behavior attached to the component first. Name conflicts caused by dif-
ferent traits requires manually resolution by renaming the affected properties
or methods.
Reasons to Use Traits
Traits are much more efficient than behaviors as behaviors are objects that
take both time and memory.
IDEs are more friendly to traits as they are language construct.
5.5 Configurations
Configurations are widely used in Yii when creating new objects or initial-
izing existing objects. Configurations usually include the class name of the
object being created, and a list of initial values that should be assigned to
the object’s properties. Configurations may also include a list of handlers
that should be attached to the object’s events and/or a list of behaviors that
should also be attached to the object.
In the following, a configuration is used to create and initialize a database
connection:
$config = [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
];
$db = Yii::createObject($config);
The Yii::createObject() method takes a configuration array as its argu-
ment, and creates an object by instantiating the class named in the config-
188 CHAPTER 5. KEY CONCEPTS
uration. When the object is instantiated, the rest of the configuration will
be used to initialize the object’s properties, event handlers, and behaviors.
If you already have an object, you may use Yii::configure() to initial-
ize the object’s properties with a configuration array:
Yii::configure($object, $config);
Note that, in this case, the configuration array should not contain a class
element.
5.5.1 Configuration Format
The format of a configuration can be formally described as:
[
’class’ => ’ClassName’,
’propertyName’ => ’propertyValue’,
’on eventName’ => $eventHandler,
’as behaviorName’ => $behaviorConfig,
]
where
• The class element specifies a fully qualified class name for the object
being created.
• The propertyName elements specify the initial values for the named prop-
erty. The keys are the property names, and the values are the corres-
ponding initial values. Only public member variables and properties
defined by getters/setters can be configured.
• The on eventName elements specify what handlers should be attached to
the object’s events. Notice that the array keys are formed by prefixing
event names with on . Please refer to the Events section for supported
event handler formats.
• The as behaviorName elements specify what behaviors should be at-
tached to the object. Notice that the array keys are formed by pre-
fixing behavior names with as ; the value, $behaviorConfig, represents
the configuration for creating a behavior, like a normal configuration
described here.
Below is an example showing a configuration with initial property values,
event handlers, and behaviors:
[
’class’ => ’appcomponentsSearchEngine’,
’apiKey’ => ’xxxxxxxx’,
’on search’ => function ($event) {
Yii::info("Keyword searched: " . $event->keyword);
},
5.5. CONFIGURATIONS 189
’as indexer’ => [
’class’ => ’appcomponentsIndexerBehavior’,
// ... property init values ...
],
]
5.5.2 Using Configurations
Configurations are used in many places in Yii. At the beginning of this
section, we have shown how to create an object according to a configura-
tion by using Yii::createObject(). In this subsection, we will describe
application configurations and widget configurations - two major usages of
configurations.
Application Configurations
Configuration for an application is probably one of the most complex config-
urations. This is because the yiiwebApplication class has a lot of config-
urable properties and events. More importantly, its yiiwebApplication
::components property can receive an array of configurations for creating
components that are registered through the application. The following is
an abstract from the application configuration file for the basic application
template.
$config = [
’id’ => ’basic’,
’basePath’ => dirname(__DIR__),
’extensions’ => require(__DIR__ . ’/../vendor/yiisoft/extensions.php’),
’components’ => [
’cache’ => [
’class’ => ’yiicachingFileCache’,
],
’mailer’ => [
’class’ => ’yiiswiftmailerMailer’,
],
’log’ => [
’class’ => ’yiilogDispatcher’,
’traceLevel’ => YII_DEBUG ? 3 : 0,
’targets’ => [
[
’class’ => ’yiilogFileTarget’,
],
],
],
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=stay2’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
190 CHAPTER 5. KEY CONCEPTS
],
],
];
The configuration does not have a class key. This is because it is used as
follows in an entry script, where the class name is already given,
(new yiiwebApplication($config))->run();
For more details about configuring the components property of an application
can be found in the Applications section and the Service Locator section.
Widget Configurations
When using widgets, you often need to use configurations to customize the
widget properties. Both of the yiibaseWidget::widget() and yiibase
Widget::begin() methods can be used to create a widget. They take a
configuration array, like the following,
use yiiwidgetsMenu;
echo Menu::widget([
’activateItems’ => false,
’items’ => [
[’label’ => ’Home’, ’url’ => [’site/index’]],
[’label’ => ’Products’, ’url’ => [’product/index’]],
[’label’ => ’Login’, ’url’ => [’site/login’], ’visible’ => Yii::$app
->user->isGuest],
],
]);
The above code creates a Menu widget and initializes its activeItems property
to be false. The items property is also configured with menu items to be
displayed.
Note that because the class name is already given, the configuration array
should NOT have the class key.
5.5.3 Configuration Files
When a configuration is very complex, a common practice is to store it in
one or multiple PHP files, known as configuration files. A configuration file
returns a PHP array representing the configuration. For example, you may
keep an application configuration in a file named web.php, like the following,
return [
’id’ => ’basic’,
’basePath’ => dirname(__DIR__),
’extensions’ => require(__DIR__ . ’/../vendor/yiisoft/extensions.php’),
’components’ => require(__DIR__ . ’/components.php’),
];
5.5. CONFIGURATIONS 191
Because the components configuration is complex too, you store it in a separate
file called components.php and “require” this file in web.php as shown above. The
content of components.php is as follows,
return [
’cache’ => [
’class’ => ’yiicachingFileCache’,
],
’mailer’ => [
’class’ => ’yiiswiftmailerMailer’,
],
’log’ => [
’class’ => ’yiilogDispatcher’,
’traceLevel’ => YII_DEBUG ? 3 : 0,
’targets’ => [
[
’class’ => ’yiilogFileTarget’,
],
],
],
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=stay2’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
],
];
To get a configuration stored in a configuration file, simply “require” it, like
the following:
$config = require(’path/to/web.php’);
(new yiiwebApplication($config))->run();
5.5.4 Default Configurations
The Yii::createObject() method is implemented based on a dependency
injection container. It allows you to specify a set of the so-called default con-
figurations which will be applied to ANY instances of the specified classes
when they are being created using Yii::createObject(). The default con-
figurations can be specified by calling Yii::$container->set() in the boot-
strapping code.
For example, if you want to customize yiiwidgetsLinkPager so that
ALL link pagers will show at most 5 page buttons (the default value is 10),
you may use the following code to achieve this goal,
Yii::$container->set(’yiiwidgetsLinkPager’, [
’maxButtonCount’ => 5,
]);
Without using default configurations, you would have to configure maxButtonCount
in every place where you use link pagers.
192 CHAPTER 5. KEY CONCEPTS
5.5.5 Environment Constants
Configurations often vary according to the environment in which an applic-
ation runs. For example, in development environment, you may want to use
a database named mydb_dev, while on production server you may want to use
the mydb_prod database. To facilitate switching environments, Yii provides
a constant named YII_ENV that you may define in the entry script of your
application. For example,
defined(’YII_ENV’) or define(’YII_ENV’, ’dev’);
You may define YII_ENV as one of the following values:
• prod: production environment. The constant YII_ENV_PROD will evaluate
as true. This is the default value of YII_ENV if you do not define it.
• dev: development environment. The constant YII_ENV_DEV will evaluate
as true.
• test: testing environment. The constant YII_ENV_TEST will evaluate as
true.
With these environment constants, you may specify your configurations con-
ditionally based on the current environment. For example, your application
configuration may contain the following code to enable the debug toolbar
and debugger in development environment.
$config = [...];
if (YII_ENV_DEV) {
// configuration adjustments for ’dev’ environment
$config[’bootstrap’][] = ’debug’;
$config[’modules’][’debug’] = ’yiidebugModule’;
}
return $config;
5.6 Aliases
Aliases are used to represent file paths or URLs so that you don’t have to
hard-code absolute paths or URLs in your project. An alias must start with
the @ character to be differentiated from normal file paths and URLs. Yii
has many pre-defined aliases already available. For example, the alias @yii
represents the installation path of the Yii framework; @web represents the
base URL for the currently running Web application.
5.6. ALIASES 193
5.6.1 Defining Aliases
You can define an alias for a file path or URL by calling Yii::setAlias():
// an alias of a file path
Yii::setAlias(’@foo’, ’/path/to/foo’);
// an alias of a URL
Yii::setAlias(’@bar’, ’http://www.example.com’);
Note: The file path or URL being aliased may not necessarily
refer to an existing file or resource.
Given a defined alias, you may derive a new alias (without the need of calling
Yii::setAlias()) by appending a slash / followed with one or more path
segments. The aliases defined via Yii::setAlias() becomes the root alias,
while aliases derived from it are derived aliases. For example, @foo is a root
alias, while @foo/bar/file.php is a derived alias.
You can define an alias using another alias (either root or derived):
Yii::setAlias(’@foobar’, ’@foo/bar’);
Root aliases are usually defined during the bootstrapping stage. For ex-
ample, you may call Yii::setAlias() in the entry script. For convenience,
Application provides a writable property named aliases that you can con-
figure in the application configuration:
return [
// ...
’aliases’ => [
’@foo’ => ’/path/to/foo’,
’@bar’ => ’http://www.example.com’,
],
];
5.6.2 Resolving Aliases
You can call Yii::getAlias() to resolve a root alias into the file path or
URL it represents. The same method can also resolve a derived alias into
the corresponding file path or URL:
echo Yii::getAlias(’@foo’); // displays: /path/to/foo
echo Yii::getAlias(’@bar’); // displays: http://www.example.
com
echo Yii::getAlias(’@foo/bar/file.php’); // displays: /path/to/foo/bar/file
.php
The path/URL represented by a derived alias is determined by replacing the
root alias part with its corresponding path/URL in the derived alias.
Note: The Yii::getAlias() method does not check whether the
resulting path/URL refers to an existing file or resource.
194 CHAPTER 5. KEY CONCEPTS
A root alias may also contain slash / characters. The Yii::getAlias()
method is intelligent enough to tell which part of an alias is a root alias and
thus correctly determines the corresponding file path or URL:
Yii::setAlias(’@foo’, ’/path/to/foo’);
Yii::setAlias(’@foo/bar’, ’/path2/bar’);
Yii::getAlias(’@foo/test/file.php’); // displays: /path/to/foo/test/file.
php
Yii::getAlias(’@foo/bar/file.php’); // displays: /path2/bar/file.php
If @foo/bar is not defined as a root alias, the last statement would display
/path/to/foo/bar/file.php.
5.6.3 Using Aliases
Aliases are recognized in many places in Yii without needing to call Yii::
getAlias() to convert them into paths or URLs. For example, yiicaching
FileCache::cachePath can accept both a file path and an alias represent-
ing a file path, thanks to the @ prefix which allows it to differentiate a file
path from an alias.
use yiicachingFileCache;
$cache = new FileCache([
’cachePath’ => ’@runtime/cache’,
]);
Please pay attention to the API documentation to see if a property or method
parameter supports aliases.
5.6.4 Predefined Aliases
Yii predefines a set of aliases to easily reference commonly used file paths
and URLs:
• @yii, the directory where the BaseYii.php file is located (also called the
framework directory)
• @app, the yiibaseApplication::basePath of the currently running
application
• @runtime, the yiibaseApplication::runtimePath of the currently
running application. Defaults to @app/runtime.
• @webroot, the Web root directory of the currently running Web applic-
ation. It is determined based on the directory containing the entry
script.
• @web, the base URL of the currently running Web application. It has
the same value as yiiwebRequest::baseUrl.
5.7. CLASS AUTOLOADING 195
• @vendor, the yiibaseApplication::vendorPath. Defaults to @app/
vendor.
• @bower, the root directory that contains bower packages4. Defaults to
@vendor/bower.
• @npm, the root directory that contains npm packages5. Defaults to
@vendor/npm.
The @yii alias is defined when you include the Yii.php file in your entry
script. The rest of the aliases are defined in the application constructor
when applying the application configuration.
5.6.5 Extension Aliases
An alias is automatically defined for each extension that is installed via
Composer. Each alias is named after the root namespace of the extension as
declared in its composer.json file, and each alias represents the root directory
of the package. For example, if you install the yiisoft/yii2-jui extension, you
will automatically have the alias @yii/jui defined during the bootstrapping
stage, equivalent to:
Yii::setAlias(’@yii/jui’, ’VendorPath/yiisoft/yii2-jui’);
5.7 Class Autoloading
Yii relies on the class autoloading mechanism6 to locate and include all
required class files. It provides a high-performance class autoloader that is
compliant to the PSR-4 standard7. The autoloader is installed when you
include the Yii.php file.
Note: For simplicity of description, in this section we will only
talk about autoloading of classes. However, keep in mind that the
content we are describing here applies to autoloading of interfaces
and traits as well.
5.7.1 Using the Yii Autoloader
To make use of the Yii class autoloader, you should follow two simple rules
when creating and naming your classes:
4
http://bower.io/
5
https://www.npmjs.org/
6
http://www.php.net/manual/en/language.oop5.autoload.php
7
https://github.com/php-fig/fig-standards/blob/master/proposed/
psr-4-autoloader/psr-4-autoloader.md
196 CHAPTER 5. KEY CONCEPTS
• Each class must be under a namespace (e.g. foobarMyClass)
• Each class must be saved in an individual file whose path is determined
by the following algorithm:
// $className is a fully qualified class name with the leading backslash
$classFile = Yii::getAlias(’@’ . str_replace(’’, ’/’, $className) . ’.php’
);
For example, if a class name and namespace is foobarMyClass, the alias for
the corresponding class file path would be @foo/bar/MyClass.php. In order for
this alias to be resolvable into a file path, either @foo or @foo/bar must be a
root alias.
When using the Basic Application Template, you may put your classes
under the top-level namespace app so that they can be autoloaded by Yii
without the need of defining a new alias. This is because @app is a predefined
alias, and a class name like appcomponentsMyClass can be resolved into the
class file AppBasePath/components/MyClass.php, according to the algorithm just
described.
In the Advanced Application Template, each tier has its own root alias.
For example, the front-end tier has a root alias @frontend, while the back-
end tier @backend. As a result, you may put the front-end classes under the
namespace frontend while the back-end classes are under backend. This will
allow these classes to be autoloaded by the Yii autoloader.
5.7.2 Class Map
The Yii class autoloader supports the class map feature, which maps class
names to the corresponding class file paths. When the autoloader is loading
a class, it will first check if the class is found in the map. If so, the corres-
ponding file path will be included directly without further check. This makes
class autoloading super fast. In fact, all core Yii classes are autoloaded this
way.
You may add a class to the class map, stored in Yii::$classMap, using:
Yii::$classMap[’foobarMyClass’] = ’path/to/MyClass.php’;
Aliases can be used to specify class file paths. You should set the class map
in the bootstrapping process so that the map is ready before your classes are
used.
5.7.3 Using Other Autoloaders
Because Yii embraces Composer as a package dependency manager, it is
recommended that you also install the Composer autoloader. If you are
using 3rd-party libraries that have their own autoloaders, you should also
install those.
5.8. SERVICE LOCATOR 197
When using the Yii autoloader together with other autoloaders, you
should include the Yii.php file after all other autoloaders are installed. This
will make the Yii autoloader the first one responding to any class autoloading
request. For example, the following code is extracted from the entry script
of the Basic Application Template. The first line installs the Composer
autoloader, while the second line installs the Yii autoloader:
require(__DIR__ . ’/../vendor/autoload.php’);
require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’);
You may use the Composer autoloader alone without the Yii autoloader.
However, by doing so, the performance of your class autoloading may be
degraded, and you must follow the rules set by Composer in order for your
classes to be autoloadable.
Info: If you do not want to use the Yii autoloader, you must
create your own version of the Yii.php file and include it in your
entry script.
5.7.4 Autoloading Extension Classes
The Yii autoloader is capable of autoloading extension classes. The sole
requirement is that an extension specifies the autoload section correctly in
its composer.json file. Please refer to the Composer documentation8 for more
details about specifying autoload.
In case you do not use the Yii autoloader, the Composer autoloader can
still autoload extension classes for you.
5.8 Service Locator
A service locator is an object that knows how to provide all sorts of services
(or components) that an application might need. Within a service locator,
each component exists as only a single instance, uniquely identified by an
ID. You use the ID to retrieve a component from the service locator.
In Yii, a service locator is simply an instance of yiidiServiceLocator,
or from a child class.
The most commonly used service locator in Yii is the application ob-
ject, which can be accessed through Yii::$app. The services it provides are
called application components, such as the request, response, and urlManager
components. You may configure these components, or even replace them
with your own implementations, easily through functionality provided by
the service locator.
Besides the application object, each module object is also a service loc-
ator.
8
https://getcomposer.org/doc/04-schema.md#autoload
198 CHAPTER 5. KEY CONCEPTS
To use a service locator, the first step is to register components with it.
A component can be registered via yiidiServiceLocator::set(). The
following code shows different ways of registering components:
use yiidiServiceLocator;
use yiicachingFileCache;
$locator = new ServiceLocator;
// register "cache" using a class name that can be used to create a
component
$locator->set(’cache’, ’yiicachingApcCache’);
// register "db" using a configuration array that can be used to create a
component
$locator->set(’db’, [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
]);
// register "search" using an anonymous function that builds a component
$locator->set(’search’, function () {
return new appcomponentsSolrService;
});
// register "pageCache" using a component
$locator->set(’pageCache’, new FileCache);
Once a component has been registered, you can access it using its ID, in one
of the two following ways:
$cache = $locator->get(’cache’);
// or alternatively
$cache = $locator->cache;
As shown above, yiidiServiceLocator allows you to access a component
like a property using the component ID. When you access a component for
the first time, yiidiServiceLocator will use the component registration
information to create a new instance of the component and return it. Later,
if the component is accessed again, the service locator will return the same
instance.
You may use yiidiServiceLocator::has() to check if a component
ID has already been registered. If you call yiidiServiceLocator::get()
with an invalid ID, an exception will be thrown.
Because service locators are often being created with configurations,
a writable property named yiidiServiceLocator::setComponents() is
provided. This allows you to configure and register multiple components at
once. The following code shows a configuration array that can be used to
configure an application, while also registering the “db”, “cache” and “search”
components:
5.9. DEPENDENCY INJECTION CONTAINER 199
return [
// ...
’components’ => [
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
],
’cache’ => ’yiicachingApcCache’,
’search’ => function () {
return new appcomponentsSolrService;
},
],
];
5.9 Dependency Injection Container
A dependency injection (DI) container is an object that knows how to in-
stantiate and configure objects and all their dependent objects. Martin’s
article9 has well explained why DI container is useful. Here we will mainly
explain the usage of the DI container provided by Yii.
5.9.1 Dependency Injection
Yii provides the DI container feature through the class yiidiContainer.
It supports the following kinds of dependency injection:
• Constructor injection;
• Setter and property injection;
• PHP callable injection.
Constructor Injection
The DI container supports constructor injection with the help of type hints
for constructor parameters. The type hints tell the container which classes
or interfaces are dependent when it is used to create a new object. The
container will try to get the instances of the dependent classes or interfaces
and then inject them into the new object through the constructor. For
example,
class Foo
{
public function __construct(Bar $bar)
{
9
http://martinfowler.com/articles/injection.html
200 CHAPTER 5. KEY CONCEPTS
}
}
$foo = $container->get(’Foo’);
// which is equivalent to the following:
$bar = new Bar;
$foo = new Foo($bar);
Setter and Property Injection
Setter and property injection is supported through configurations. When
registering a dependency or when creating a new object, you can provide a
configuration which will be used by the container to inject the dependencies
through the corresponding setters or properties. For example,
use yiibaseObject;
class Foo extends Object
{
public $bar;
private $_qux;
public function getQux()
{
return $this->_qux;
}
public function setQux(Qux $qux)
{
$this->_qux = $qux;
}
}
$container->get(’Foo’, [], [
’bar’ => $container->get(’Bar’),
’qux’ => $container->get(’Qux’),
]);
PHP Callable Injection
In this case, the container will use a registered PHP callable to build new
instances of a class. The callable is responsible to resolve the dependencies
and inject them appropriately to the newly created objects. For example,
$container->set(’Foo’, function () {
return new Foo(new Bar);
});
$foo = $container->get(’Foo’);
5.9. DEPENDENCY INJECTION CONTAINER 201
5.9.2 Registering Dependencies
You can use yiidiContainer::set() to register dependencies. The re-
gistration requires a dependency name as well as a dependency definition. A
dependency name can be a class name, an interface name, or an alias name;
and a dependency definition can be a class name, a configuration array, or a
PHP callable.
$container = new yiidiContainer;
// register a class name as is. This can be skipped.
$container->set(’yiidbConnection’);
// register an interface
// When a class depends on the interface, the corresponding class
// will be instantiated as the dependent object
$container->set(’yiimailMailInterface’, ’yiiswiftmailerMailer’);
// register an alias name. You can use $container->get(’foo’)
// to create an instance of Connection
$container->set(’foo’, ’yiidbConnection’);
// register a class with configuration. The configuration
// will be applied when the class is instantiated by get()
$container->set(’yiidbConnection’, [
’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
]);
// register an alias name with class configuration
// In this case, a "class" element is required to specify the class
$container->set(’db’, [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
]);
// register a PHP callable
// The callable will be executed each time when $container->get(’db’) is
called
$container->set(’db’, function ($container, $params, $config) {
return new yiidbConnection($config);
});
// register a component instance
// $container->get(’pageCache’) will return the same instance each time it
is called
$container->set(’pageCache’, new FileCache);
202 CHAPTER 5. KEY CONCEPTS
Tip: If a dependency name is the same as the corresponding
dependency definition, you do not need to register it with the DI
container.
A dependency registered via set() will generate an instance each time the
dependency is needed. You can use yiidiContainer::setSingleton()
to register a dependency that only generates a single instance:
$container->setSingleton(’yiidbConnection’, [
’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’,
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
]);
5.9.3 Resolving Dependencies
Once you have registered dependencies, you can use the DI container to
create new objects, and the container will automatically resolve dependencies
by instantiating them and injecting them into the newly created objects. The
dependency resolution is recursive, meaning that if a dependency has other
dependencies, those dependencies will also be resolved automatically.
You can use yiidiContainer::get() to create new objects. The
method takes a dependency name, which can be a class name, an inter-
face name or an alias name. The dependency name may or may not be
registered via set() or setSingleton(). You may optionally provide a list
of class constructor parameters and a configuration to configure the newly
created object. For example,
// "db" is a previously registered alias name
$db = $container->get(’db’);
// equivalent to: $engine = new appcomponentsSearchEngine($apiKey, [’type
’ => 1]);
$engine = $container->get(’appcomponentsSearchEngine’, [$apiKey], [’type’
=> 1]);
Behind the scene, the DI container does much more work than just creating
a new object. The container will first inspect the class constructor to find
out dependent class or interface names and then automatically resolve those
dependencies recursively.
The following code shows a more sophisticated example. The UserLister
class depends on an object implementing the UserFinderInterface interface;
the UserFinder class implements this interface and depends on a Connection
object. All these dependencies are declared through type hinting of the
class constructor parameters. With property dependency registration, the
DI container is able to resolve these dependencies automatically and creates
a new UserLister instance with a simple call of get(’userLister’).
5.9. DEPENDENCY INJECTION CONTAINER 203
namespace appmodels;
use yiibaseObject;
use yiidbConnection;
use yiidiContainer;
interface UserFinderInterface
{
function findUser();
}
class UserFinder extends Object implements UserFinderInterface
{
public $db;
public function __construct(Connection $db, $config = [])
{
$this->db = $db;
parent::__construct($config);
}
public function findUser()
{
}
}
class UserLister extends Object
{
public $finder;
public function __construct(UserFinderInterface $finder, $config = [])
{
$this->finder = $finder;
parent::__construct($config);
}
}
$container = new Container;
$container->set(’yiidbConnection’, [
’dsn’ => ’...’,
]);
$container->set(’appmodelsUserFinderInterface’, [
’class’ => ’appmodelsUserFinder’,
]);
$container->set(’userLister’, ’appmodelsUserLister’);
$lister = $container->get(’userLister’);
// which is equivalent to:
$db = new yiidbConnection([’dsn’ => ’...’]);
$finder = new UserFinder($db);
$lister = new UserLister($finder);
204 CHAPTER 5. KEY CONCEPTS
5.9.4 Practical Usage
Yii creates a DI container when you include the Yii.php file in the entry script
of your application. The DI container is accessible via Yii::$container.
When you call Yii::createObject(), the method will actually call the con-
tainer’s yiidiContainer::get() method to create a new object. As afore-
mentioned, the DI container will automatically resolve the dependencies (if
any) and inject them into the newly created object. Because Yii uses Yii::
createObject() in most of its core code to create new objects, this means
you can customize the objects globally by dealing with Yii::$container.
For example, you can customize globally the default number of pagination
buttons of yiiwidgetsLinkPager:
Yii::$container->set(’yiiwidgetsLinkPager’, [’maxButtonCount’ => 5]);
Now if you use the widget in a view with the following code, the maxButtonCount
property will be initialized as 5 instead of the default value 10 as defined in
the class.
echo yiiwidgetsLinkPager::widget();
You can still override the value set via DI container, though:
echo yiiwidgetsLinkPager::widget([’maxButtonCount’ => 20]);
Another example is to take advantage of the automatic constructor injection
of the DI container. Assume your controller class depends on some other
objects, such as a hotel booking service. You can declare the dependency
through a constructor parameter and let the DI container to resolve it for
you.
namespace appcontrollers;
use yiiwebController;
use appcomponentsBookingInterface;
class HotelController extends Controller
{
protected $bookingService;
public function __construct($id, $module, BookingInterface
$bookingService, $config = [])
{
$this->bookingService = $bookingService;
parent::__construct($id, $module, $config);
}
}
If you access this controller from browser, you will see an error complaining
the BookingInterface cannot be instantiated. This is because you need to tell
the DI container how to deal with this dependency:
Yii::$container->set(’appcomponentsBookingInterface’, ’appcomponents
BookingService’);
5.9. DEPENDENCY INJECTION CONTAINER 205
Now if you access the controller again, an instance of appcomponentsBookingService
will be created and injected as the 3rd parameter to the controller’s con-
structor.
5.9.5 When to Register Dependencies
Because dependencies are needed when new objects are being created, their
registration should be done as early as possible. The followings are the
recommended practices:
• If you are the developer of an application, you can register dependencies
in your application’s entry script or in a script that is included by the
entry script.
• If you are the developer of a redistributable extension, you can register
dependencies in the bootstrapping class of the extension.
5.9.6 Summary
Both dependency injection and service locator are popular design patterns
that allow building software in a loosely-coupled and more testable fash-
ion. We highly recommend you to read Martin’s article10 to get a deeper
understanding of dependency injection and service locator.
Yii implements its service locator on top of the dependency injection
(DI) container. When a service locator is trying to create a new object
instance, it will forward the call to the DI container. The latter will resolve
the dependencies automatically as described above.
10
http://martinfowler.com/articles/injection.html
206 CHAPTER 5. KEY CONCEPTS
Chapter 6
Working with Databases
6.1 Database basics
Note: This section is under development.
Yii has a database access layer built on top of PHP’s PDO1. It provides
uniform API and solves some inconsistencies between different DBMS. By
default Yii supports the following DBMS:
• MySQL2
• MariaDB3
• SQLite4
• PostgreSQL5
• CUBRID6: version 9.1.0 or higher.
• Oracle7
• MSSQL8: version 2005 or higher.
6.1.1 Configuration
In order to start using database you need to configure database connection
component first by adding db component to application configuration (for
“basic” web application it’s config/web.php) like the following:
1
http://www.php.net/manual/en/book.pdo.php
2
http://www.mysql.com/
3
https://mariadb.com/
4
http://sqlite.org/
5
http://www.postgresql.org/
6
http://www.cubrid.org/
7
http://www.oracle.com/us/products/database/overview/index.html
8
https://www.microsoft.com/en-us/sqlserver/default.aspx
207
208 CHAPTER 6. WORKING WITH DATABASES
return [
// ...
’components’ => [
// ...
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=mydatabase’, // MySQL,
MariaDB
//’dsn’ => ’sqlite:/path/to/database/file’, // SQLite
//’dsn’ => ’pgsql:host=localhost;port=5432;dbname=mydatabase’,
// PostgreSQL
//’dsn’ => ’cubrid:dbname=demodb;host=localhost;port=33000’, //
CUBRID
//’dsn’ => ’sqlsrv:Server=localhost;Database=mydatabase’, // MS
SQL Server, sqlsrv driver
//’dsn’ => ’dblib:host=localhost;dbname=mydatabase’, // MS SQL
Server, dblib driver
//’dsn’ => ’mssql:host=localhost;dbname=mydatabase’, // MS SQL
Server, mssql driver
//’dsn’ => ’oci:dbname=//localhost:1521/mydatabase’, // Oracle
’username’ => ’root’,
’password’ => ’’,
’charset’ => ’utf8’,
],
],
// ...
];
There is a peculiarity when you want to work with the database through the
ODBC layer. When using ODBC, connection DSN doesn’t indicate uniquely what
database type is being used. That’s why you have to override driverName
property of yiidbConnection class to disambiguate that:
’db’ => [
’class’ => ’yiidbConnection’,
’driverName’ => ’mysql’,
’dsn’ => ’odbc:Driver={MySQL};Server=localhost;Database=test’,
’username’ => ’root’,
’password’ => ’’,
],
Please refer to the PHP manual9 for more details on the format of the DSN
string.
After the connection component is configured you can access it using the
following syntax:
$connection = Yii::$app->db;
You can refer to yiidbConnection for a list of properties you can con-
figure. Also note that you can define more than one connection component
and use both at the same time if needed:
9
http://www.php.net/manual/en/function.PDO-construct.php
6.1. DATABASE BASICS 209
$primaryConnection = Yii::$app->db;
$secondaryConnection = Yii::$app->secondDb;
If you don’t want to define the connection as an application component you
can instantiate it directly:
$connection = new yiidbConnection([
’dsn’ => $dsn,
’username’ => $username,
’password’ => $password,
]);
$connection->open();
Tip: if you need to execute additional SQL queries right after
establishing a connection you can add the following to your ap-
plication configuration file:
return [
// ...
’components’ => [
// ...
’db’ => [
’class’ => ’yiidbConnection’,
// ...
’on afterOpen’ => function($event) {
$event->sender->createCommand("SET time_zone = ’
UTC’")->execute();
}
],
],
// ...
];
6.1.2 Basic SQL queries
Once you have a connection instance you can execute SQL queries using yii
dbCommand.
SELECT
When query returns a set of rows:
$command = $connection->createCommand(’SELECT * FROM post’);
$posts = $command->queryAll();
When only a single row is returned:
$command = $connection->createCommand(’SELECT * FROM post WHERE id=1’);
$post = $command->queryOne();
When there are multiple values from the same column:
$command = $connection->createCommand(’SELECT title FROM post’);
$titles = $command->queryColumn();
210 CHAPTER 6. WORKING WITH DATABASES
When there’s a scalar value:
$command = $connection->createCommand(’SELECT COUNT(*) FROM post’);
$postCount = $command->queryScalar();
UPDATE, INSERT, DELETE etc.
If SQL executed doesn’t return any data you can use command’s execute
method:
$command = $connection->createCommand(’UPDATE post SET status=1 WHERE id=1’)
;
$command->execute();
Alternatively the following syntax that takes care of proper table and column
names quoting is possible:
// INSERT
$connection->createCommand()->insert(’user’, [
’name’ => ’Sam’,
’age’ => 30,
])->execute();
// INSERT multiple rows at once
$connection->createCommand()->batchInsert(’user’, [’name’, ’age’], [
[’Tom’, 30],
[’Jane’, 20],
[’Linda’, 25],
])->execute();
// UPDATE
$connection->createCommand()->update(’user’, [’status’ => 1], ’age > 30’)->
execute();
// DELETE
$connection->createCommand()->delete(’user’, ’status = 0’)->execute();
6.1.3 Quoting table and column names
Most of the time you would use the following syntax for quoting table and
column names:
$sql = "SELECT COUNT([[$column]]) FROM {{table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();
In the code above [[X]] will be converted to properly quoted column name
while {{Y}} will be converted to properly quoted table name.
For table names there’s a special variant {{%Y}} that allows you to auto-
matically appending table prefix if it is set:
$sql = "SELECT COUNT([[$column]]) FROM {{%table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();
6.1. DATABASE BASICS 211
The code above will result in selecting from tbl_table if you have table prefix
configured like the following in your config file:
return [
// ...
’components’ => [
// ...
’db’ => [
// ...
’tablePrefix’ => ’tbl_’,
],
],
];
The alternative is to quote table and column names manually using yiidb
Connection::quoteTableName() and yiidbConnection::quoteColumnName():
$column = $connection->quoteColumnName($column);
$table = $connection->quoteTableName($table);
$sql = "SELECT COUNT($column) FROM $table";
$rowCount = $connection->createCommand($sql)->queryScalar();
6.1.4 Prepared statements
In order to securely pass query parameters you can use prepared statements:
$command = $connection->createCommand(’SELECT * FROM post WHERE id=:id’);
$command->bindValue(’:id’, $_GET[’id’]);
$post = $command->query();
Another usage is performing a query multiple times while preparing it only
once:
$command = $connection->createCommand(’DELETE FROM post WHERE id=:id’);
$command->bindParam(’:id’, $id);
$id = 1;
$command->execute();
$id = 2;
$command->execute();
6.1.5 Transactions
When running multiple related queries in a sequence you may need to wrap
them in a transaction to ensure you data is consistent. Yii provides a simple
interface to work with transactions in simple cases but also for advanced
usage when you need to define isolation levels.
The following code shows a simple pattern that all code that uses trans-
actional queries should follow:
212 CHAPTER 6. WORKING WITH DATABASES
$transaction = $connection->beginTransaction();
try {
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
// ... executing other SQL statements ...
$transaction->commit();
} catch(Exception $e) {
$transaction->rollBack();
throw $e;
}
The first line starts a new transaction using the yiidbConnection::beginTransaction()-
method of the database connection object. The transaction itself is repres-
ented by a yiidbTransaction object stored in $transaction. We wrap the
execution of all queries in a try-catch-block to be able to handle errors. We
call yiidbTransaction::commit() on success to commit the transaction
and yiidbTransaction::rollBack() in case of an error. This will revert
the effect of all queries that have been executed inside of the transaction.
throw $e is used to re-throw the exception in case we can not handle the error
ourselves and delegate it to some other code or the yii error handler.
It is also possible to nest multiple transactions, if needed:
// outer transaction
$transaction1 = $connection->beginTransaction();
try {
$connection->createCommand($sql1)->execute();
// inner transaction
$transaction2 = $connection->beginTransaction();
try {
$connection->createCommand($sql2)->execute();
$transaction2->commit();
} catch (Exception $e) {
$transaction2->rollBack();
}
$transaction1->commit();
} catch (Exception $e) {
$transaction1->rollBack();
}
Note that your DBMS should have support for Savepoints for this to work as
expected. The above code will work for any DBMS but transactional safety
is only guaranteed if the underlying DBMS supports it.
Yii also supports setting isolation levels10 for your transactions. When
beginning a transaction it will run in the default isolation level set by you
database system. You can specifying an isolation level explicitly when start-
ing a transaction:
10
http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_
levels
6.1. DATABASE BASICS 213
$transaction = $connection->beginTransaction(yiidbTransaction::
REPEATABLE_READ);
Yii provides four constants for the most common isolation levels:
• yiidbTransaction::READ_UNCOMMITTED - the weakest level, Dirty
reads, Non-repeatable reads and Phantoms may occur.
• yiidbTransaction::READ_COMMITTED - avoid Dirty reads.
• yiidbTransaction::REPEATABLE_READ - avoid Dirty reads and Non-
repeatable reads.
• yiidbTransaction::SERIALIZABLE - the strongest level, avoids all
of the above named problems.
You may use the constants named above but you can also use a string
that represents a valid syntax that can be used in your DBMS follow-
ing SET TRANSACTION ISOLATION LEVEL. For postgres this could be for example
SERIALIZABLE READ ONLY DEFERRABLE.
Note that some DBMS allow setting of the isolation level only for the
whole connection so subsequent transactions may get the same isolation level
even if you did not specify any. When using this feature you may need to set
the isolation level for all transactions explicitly to avoid conflicting settings.
At the time of this writing affected DBMS are MSSQL and SQLite.
Note: SQLite only supports two isolation levels, so you can only
use READ UNCOMMITTED and SERIALIZABLE. Usage of other levels will
result in an exception to be thrown.
Note: PostgreSQL does not allow setting the isolation level before
the transaction starts so you can not specify the isolation level
directly when starting the transaction. You have to call yii
dbTransaction::setIsolationLevel() in this case after the
transaction has started.
6.1.6 Replication and Read-Write Splitting
Many DBMS support database replication11 to get better database availab-
ility and faster server response time. With database replication, data are
replicated from the so-called master servers to slave servers. All writes and
updates must take place on the master servers, while reads may take place
on the slave servers.
To take advantage of database replication and achieve read-write split-
ting, you can configure a yiidbConnection component like the following:
11
http://en.wikipedia.org/wiki/Replication_(computing)#Database_
replication
214 CHAPTER 6. WORKING WITH DATABASES
[
’class’ => ’yiidbConnection’,
// configuration for the master
’dsn’ => ’dsn for master server’,
’username’ => ’master’,
’password’ => ’’,
// common configuration for slaves
’slaveConfig’ => [
’username’ => ’slave’,
’password’ => ’’,
’attributes’ => [
// use a smaller connection timeout
PDO::ATTR_TIMEOUT => 10,
],
],
// list of slave configurations
’slaves’ => [
[’dsn’ => ’dsn for slave server 1’],
[’dsn’ => ’dsn for slave server 2’],
[’dsn’ => ’dsn for slave server 3’],
[’dsn’ => ’dsn for slave server 4’],
],
]
The above configuration specifies a setup with a single master and multiple
slaves. One of the slaves will be connected and used to perform read queries,
while the master will be used to perform write queries. Such read-write
splitting is accomplished automatically with this configuration. For example,
// create a Connection instance using the above configuration
$db = Yii::createObject($config);
// query against one of the slaves
$rows = $db->createCommand(’SELECT * FROM user LIMIT 10’)->queryAll();
// query against the master
$db->createCommand("UPDATE user SET username=’demo’ WHERE id=1")->execute();
Info: Queries performed by calling yiidbCommand::execute()
are considered as write queries, while all other queries done through
one of the “query” method of yiidbCommand are read queries.
You can get the currently active slave connection via $db->slave.
The Connection component supports load balancing and failover about slaves.
When performing a read query for the first time, the Connection component
will randomly pick a slave and try connecting to it. If the slave is found
“dead”, it will try another one. If none of the slaves is available, it will connect
to the master. By configuring a yiidbConnection::serverStatusCache,
6.1. DATABASE BASICS 215
a “dead” server can be remembered so that it will not be tried again during
a yiidbConnection::serverRetryInterval.
Info: In the above configuration, a connection timeout of 10
seconds is specified for every slave. This means if a slave cannot
be reached in 10 seconds, it is considered as “dead”. You can
adjust this parameter based on your actual environment.
You can also configure multiple masters with multiple slaves. For example,
[
’class’ => ’yiidbConnection’,
// common configuration for masters
’masterConfig’ => [
’username’ => ’master’,
’password’ => ’’,
’attributes’ => [
// use a smaller connection timeout
PDO::ATTR_TIMEOUT => 10,
],
],
// list of master configurations
’masters’ => [
[’dsn’ => ’dsn for master server 1’],
[’dsn’ => ’dsn for master server 2’],
],
// common configuration for slaves
’slaveConfig’ => [
’username’ => ’slave’,
’password’ => ’’,
’attributes’ => [
// use a smaller connection timeout
PDO::ATTR_TIMEOUT => 10,
],
],
// list of slave configurations
’slaves’ => [
[’dsn’ => ’dsn for slave server 1’],
[’dsn’ => ’dsn for slave server 2’],
[’dsn’ => ’dsn for slave server 3’],
[’dsn’ => ’dsn for slave server 4’],
],
]
The above configuration specifies two masters and four slaves. The Connection
component also supports load balancing and failover about masters, like that
about slaves. A difference is that in case none of the masters is available, an
exception will be thrown.
216 CHAPTER 6. WORKING WITH DATABASES
Note: When you use the yiidbConnection::masters prop-
erty to configure one or multiple masters, all other properties for
specifying a database connection (e.g. dsn, username, password)
with the Connection object itself will be ignored.
By default, transactions use the master connection. And within a transac-
tion, all DB operations will use the master connection. For example,
// the transaction is started on the master connection
$transaction = $db->beginTransaction();
try {
// both queries are performed against the master
$rows = $db->createCommand(’SELECT * FROM user LIMIT 10’)->queryAll();
$db->createCommand("UPDATE user SET username=’demo’ WHERE id=1")->
execute();
$transaction->commit();
} catch(Exception $e) {
$transaction->rollBack();
throw $e;
}
If you want to start a transaction with the slave connection, you should
explicitly do so, like the following:
$transaction = $db->slave->beginTransaction();
Sometimes, you may want to force using the master connection to perform
a read query. This can be achieved with the useMaster() method:
$rows = $db->useMaster(function ($db) {
return $db->createCommand(’SELECT * FROM user LIMIT 10’)->queryAll();
});
You may also directly set $db->enableSlaves to be false to direct all queries
to the master connection.
6.1.7 Working with database schema
Getting schema information
You can get a yiidbSchema instance like the following:
$schema = $connection->getSchema();
It contains a set of methods allowing you to retrieve various information
about the database:
$tables = $schema->getTableNames();
For the full reference check yiidbSchema.
6.2. QUERY BUILDER AND QUERY 217
Modifying schema
Aside from basic SQL queries yiidbCommand contains a set of methods
allowing to modify database schema:
• createTable, renameTable, dropTable, truncateTable
• addColumn, renameColumn, dropColumn, alterColumn
• addPrimaryKey, dropPrimaryKey
• addForeignKey, dropForeignKey
• createIndex, dropIndex
These can be used as follows:
// CREATE TABLE
$connection->createCommand()->createTable(’post’, [
’id’ => ’pk’,
’title’ => ’string’,
’text’ => ’text’,
]);
For the full reference check yiidbCommand.
6.2 Query Builder and Query
Note: This section is under development.
Yii provides a basic database access layer as described in the Database ba-
sics section. The database access layer provides a low-level way to interact
with the database. While useful in some situations, it can be tedious and
error-prone to write raw SQLs. An alternative approach is to use the Query
Builder. The Query Builder provides an object-oriented vehicle for generat-
ing queries to be executed.
A typical usage of the query builder looks like the following:
$rows = (new yiidbQuery())
->select(’id, name’)
->from(’user’)
->limit(10)
->all();
// which is equivalent to the following code:
$query = (new yiidbQuery())
->select(’id, name’)
->from(’user’)
->limit(10);
218 CHAPTER 6. WORKING WITH DATABASES
// Create a command. You can get the actual SQL using $command->sql
$command = $query->createCommand();
// Execute the command:
$rows = $command->queryAll();
6.2.1 Query Methods
As you can see, yiidbQuery is the main player that you need to deal
with. Behind the scene, Query is actually only responsible for representing
various query information. The actual query building logic is done by yii
dbQueryBuilder when you call the createCommand() method, and the query
execution is done by yiidbCommand.
For convenience, yiidbQuery provides a set of commonly used query
methods that will build the query, execute it, and return the result. For
example,
• yiidbQuery::all(): builds the query, executes it and returns all
results as an array.
• yiidbQuery::one(): returns the first row of the result.
• yiidbQuery::column(): returns the first column of the result.
• yiidbQuery::scalar(): returns the first column in the first row of
the result.
• yiidbQuery::exists(): returns a value indicating whether the query
results in anything.
• yiidbQuery::count(): returns the result of a COUNT query. Other
similar methods include sum($q), average($q), max($q), min($q), which
support the so-called aggregational data query. $q parameter is man-
datory for these methods and can be either the column name or ex-
pression.
6.2.2 Building Query
In the following, we will explain how to build various clauses in a SQL
statement. For simplicity, we use $query to represent a yiidbQuery object.
SELECT
In order to form a basic SELECT query, you need to specify what columns to
select and from what table:
$query->select(’id, name’)
->from(’user’);
6.2. QUERY BUILDER AND QUERY 219
Select options can be specified as a comma-separated string, as in the above,
or as an array. The array syntax is especially useful when forming the selec-
tion dynamically:
$query->select([’id’, ’name’])
->from(’user’);
Info: You should always use the array format if your SELECT clause
contains SQL expressions. This is because a SQL expression like
CONCAT(first_name, last_name) AS full_name may contain commas.
If you list it together with other columns in a string, the expres-
sion may be split into several parts by commas, which is not what
you want to see.
When specifying columns, you may include the table prefixes or column
aliases, e.g., user.id, user.id AS user_id. If you are using array to specify the
columns, you may also use the array keys to specify the column aliases, e.g.,
[’user_id’ => ’user.id’, ’user_name’ => ’user.name’].
To select distinct rows, you may call distinct(), like the following:
$query->select(’user_id’)->distinct()->from(’post’);
FROM
To specify which table(s) to select data from, call from():
$query->select(’*’)->from(’user’);
You may specify multiple tables using a comma-separated string or an array.
Table names can contain schema prefixes (e.g. ’public.user’) and/or table
aliases (e.g. ’user u’). The method will automatically quote the table names
unless it contains some parenthesis (which means the table is given as a sub-
query or DB expression). For example,
$query->select(’u.*, p.*’)->from([’user u’, ’post p’]);
When the tables are specified as an array, you may also use the array keys
as the table aliases (if a table does not need alias, do not use a string key).
For example,
$query->select(’u.*, p.*’)->from([’u’ => ’user’, ’p’ => ’post’]);
You may specify a sub-query using a Query object. In this case, the corres-
ponding array key will be used as the alias for the sub-query.
$subQuery = (new Query())->select(’id’)->from(’user’)->where(’status=1’);
$query->select(’*’)->from([’u’ => $subQuery]);
220 CHAPTER 6. WORKING WITH DATABASES
WHERE
Usually data is selected based upon certain criteria. Query Builder has some
useful methods to specify these, the most powerful of which being where. It
can be used in multiple ways.
The simplest way to apply a condition is to use a string:
$query->where(’status=:status’, [’:status’ => $status]);
When using strings, make sure you’re binding the query parameters, not
creating a query by string concatenation. The above approach is safe to use,
the following is not:
$query->where("status=$status"); // Dangerous!
Instead of binding the status value immediately, you can do so using params
or addParams:
$query->where(’status=:status’);
$query->addParams([’:status’ => $status]);
Multiple conditions can simultaneously be set in where using the hash format:
$query->where([
’status’ => 10,
’type’ => 2,
’id’ => [4, 8, 15, 16, 23, 42],
]);
That code will generate the following SQL:
WHERE (‘status‘ = 10) AND (‘type‘ = 2) AND (‘id‘ IN (4, 8, 15, 16, 23, 42))
NULL is a special value in databases, and is handled smartly by the Query
Builder. This code:
$query->where([’status’ => null]);
results in this WHERE clause:
WHERE (‘status‘ IS NULL)
You can also create sub-queries with Query objects like the following,
$userQuery = (new Query)->select(’id’)->from(’user’);
$query->where([’id’ => $userQuery]);
which will generate the following SQL:
WHERE ‘id‘ IN (SELECT ‘id‘ FROM ‘user‘)
Another way to use the method is the operand format which is [operator,
operand1, operand2, ...].
Operator can be one of the following:
• and: the operands should be concatenated together using AND. For
example, [’and’, ’id=1’, ’id=2’] will generate id=1 AND id=2. If an op-
erand is an array, it will be converted into a string using the rules
6.2. QUERY BUILDER AND QUERY 221
described here. For example, [’and’, ’type=1’, [’or’, ’id=1’, ’id=2’
]] will generate type=1 AND (id=1 OR id=2). The method will NOT do
any quoting or escaping.
• or: similar to the and operator except that the operands are concaten-
ated using OR.
• between: operand 1 should be the column name, and operand 2 and 3
should be the starting and ending values of the range that the column
is in. For example, [’between’, ’id’, 1, 10] will generate id BETWEEN 1
AND 10.
• not between: similar to between except the BETWEEN is replaced with NOT
BETWEEN in the generated condition.
• in: operand 1 should be a column or DB expression. Operand 2 can
be either an array or a Query object. It will generate an IN condition.
If Operand 2 is an array, it will represent the range of the values that
the column or DB expression should be; If Operand 2 is a Query object,
a sub-query will be generated and used as the range of the column
or DB expression. For example, [’in’, ’id’, [1, 2, 3]] will generate
id IN (1, 2, 3). The method will properly quote the column name and
escape values in the range. The in operator also supports composite
columns. In this case, operand 1 should be an array of the columns,
while operand 2 should be an array of arrays or a Query object repres-
enting the range of the columns.
• not in: similar to the in operator except that IN is replaced with NOT IN
in the generated condition.
• like: operand 1 should be a column or DB expression, and operand 2
be a string or an array representing the values that the column or DB
expression should be like. For example, [’like’, ’name’, ’tester’] will
generate name LIKE ’%tester%’. When the value range is given as an ar-
ray, multiple LIKE predicates will be generated and concatenated using
AND. For example, [’like’, ’name’, [’test’, ’sample’]] will generate
name LIKE ’%test%’ AND name LIKE ’%sample%’. You may also provide an
optional third operand to specify how to escape special characters in
the values. The operand should be an array of mappings from the
special characters to their escaped counterparts. If this operand is not
provided, a default escape mapping will be used. You may use false
or an empty array to indicate the values are already escaped and no
escape should be applied. Note that when using an escape mapping
(or the third operand is not provided), the values will be automatically
enclosed within a pair of percentage characters.
222 CHAPTER 6. WORKING WITH DATABASES
Note: When using PostgreSQL you may also use ilike12
instead of like for case-insensitive matching.
• or like: similar to the like operator except that OR is used to concat-
enate the LIKE predicates when operand 2 is an array.
• not like: similar to the like operator except that LIKE is replaced with
NOT LIKE in the generated condition.
• or not like: similar to the not like operator except that OR is used to
concatenate the NOT LIKE predicates.
• exists: requires one operand which must be an instance of yiidb
Query representing the sub-query. It will build a EXISTS (sub-query)
expression.
• not exists: similar to the exists operator and builds a NOT EXISTS (sub
-query) expression.
Additionally you can specify anything as operator:
$userQuery = (new Query)->select(’id’)->from(’user’);
$query->where([’>=’, ’id’, 10]);
It will result in:
SELECT id FROM user WHERE id >= 10;
If you are building parts of condition dynamically it’s very convenient to use
andWhere() and orWhere():
$status = 10;
$search = ’yii’;
$query->where([’status’ => $status]);
if (!empty($search)) {
$query->andWhere([’like’, ’title’, $search]);
}
In case $search isn’t empty the following SQL will be generated:
WHERE (‘status‘ = 10) AND (‘title‘ LIKE ’%yii%’)
Building Filter Conditions When building filter conditions based on
user inputs, you usually want to specially handle “empty inputs” by ignoring
them in the filters. For example, you have an HTML form that takes user-
name and email inputs. If the user only enters something in the username
input, you may want to build a query that only tries to match the entered
username. You may use the filterWhere() method achieve this goal:
12
http://www.postgresql.org/docs/8.3/static/functions-matching.html#
FUNCTIONS-LIKE
6.2. QUERY BUILDER AND QUERY 223
// $username and $email are from user inputs
$query->filterWhere([
’username’ => $username,
’email’ => $email,
]);
The filterWhere() method is very similar to where(). The main difference is
that filterWhere() will remove empty values from the provided condition. So
if $email is “empty”, the resulting query will be ...WHERE username=:username;
and if both $username and $email are “empty”, the query will have no WHERE
part.
A value is empty if it is null, an empty string, a string consisting of
whitespaces, or an empty array.
You may also use andFilterWhere() and orFilterWhere() to append more
filter conditions.
ORDER BY
For ordering results orderBy and addOrderBy could be used:
$query->orderBy([
’id’ => SORT_ASC,
’name’ => SORT_DESC,
]);
Here we are ordering by id ascending and then by name descending.
GROUP BY and HAVING
In order to add GROUP BY to generated SQL you can use the following:
$query->groupBy(’id, status’);
If you want to add another field after using groupBy:
$query->addGroupBy([’created_at’, ’updated_at’]);
To add a HAVING condition the corresponding having method and its andHaving
and orHaving can be used. Parameters for these are similar to the ones for
where methods group:
$query->having([’status’ => $status]);
LIMIT and OFFSET
To limit result to 10 rows limit can be used:
$query->limit(10);
To skip 100 fist rows use:
$query->offset(100);
224 CHAPTER 6. WORKING WITH DATABASES
JOIN
The JOIN clauses are generated in the Query Builder by using the applicable
join method:
• innerJoin()
• leftJoin()
• rightJoin()
This left join selects data from two related tables in one query:
$query->select([’user.name AS author’, ’post.title as title’])
->from(’user’)
->leftJoin(’post’, ’post.user_id = user.id’);
In the code, the leftJoin() method’s first parameter specifies the table to
join to. The second parameter defines the join condition.
If your database application supports other join types, you can use those
via the generic join method:
$query->join(’FULL OUTER JOIN’, ’post’, ’post.user_id = user.id’);
The first argument is the join type to perform. The second is the table to
join to, and the third is the condition.
Like FROM, you may also join with sub-queries. To do so, specify the sub-
query as an array which must contain one element. The array value must
be a Query object representing the sub-query, while the array key is the alias
for the sub-query. For example,
$query->leftJoin([’u’ => $subQuery], ’u.id=author_id’);
UNION
UNION in SQL adds results of one query to results of another query. Columns
returned by both queries should match. In Yii in order to build it you can
first form two query objects and then use union method:
$query = new Query();
$query->select("id, category_id as type, name")->from(’post’)->limit(10);
$anotherQuery = new Query();
$anotherQuery->select(’id, type, name’)->from(’user’)->limit(10);
$query->union($anotherQuery);
6.2. QUERY BUILDER AND QUERY 225
6.2.3 Batch Query
When working with large amount of data, methods such as yiidbQuery::
all() are not suitable because they require loading all data into the memory.
To keep the memory requirement low, Yii provides the so-called batch query
support. A batch query makes uses of data cursor and fetches data in
batches.
Batch query can be used like the following:
use yiidbQuery;
$query = (new Query())
->from(’user’)
->orderBy(’id’);
foreach ($query->batch() as $users) {
// $users is an array of 100 or fewer rows from the user table
}
// or if you want to iterate the row one by one
foreach ($query->each() as $user) {
// $user represents one row of data from the user table
}
The method yiidbQuery::batch() and yiidbQuery::each() return an
yiidbBatchQueryResult object which implements the Iterator interface
and thus can be used in the foreach construct. During the first iteration, a
SQL query is made to the database. Data are since then fetched in batches
in the iterations. By default, the batch size is 100, meaning 100 rows of data
are being fetched in each batch. You can change the batch size by passing
the first parameter to the batch() or each() method.
Compared to the yiidbQuery::all(), the batch query only loads 100
rows of data at a time into the memory. If you process the data and then
discard it right away, the batch query can help keep the memory usage under
a limit.
If you specify the query result to be indexed by some column via yiidb
Query::indexBy(), the batch query will still keep the proper index. For
example,
use yiidbQuery;
$query = (new Query())
->from(’user’)
->indexBy(’username’);
foreach ($query->batch() as $users) {
// $users is indexed by the "username" column
}
foreach ($query->each() as $username => $user) {
}
226 CHAPTER 6. WORKING WITH DATABASES
6.3 Active Record
Note: This section is under development.
Active Record13 provides an object-oriented interface for accessing data
stored in a database. An Active Record class is associated with a database
table, an Active Record instance corresponds to a row of that table, and an
attribute of an Active Record instance represents the value of a column in
that row. Instead of writing raw SQL statements, you can work with Act-
ive Record in an object-oriented fashion to manipulate the data in database
tables.
For example, assume Customer is an Active Record class is associated with
the customer table and name is a column of customer table. You can write the
following code to insert a new row into customer table:
$customer = new Customer();
$customer->name = ’Qiang’;
$customer->save();
The above code is equivalent to using the following raw SQL statement,
which is less intuitive, more error prone, and may have compatibility prob-
lems for different DBMS:
$db->createCommand(’INSERT INTO customer (name) VALUES (:name)’, [
’:name’ => ’Qiang’,
])->execute();
Below is the list of databases that are currently supported by Yii Active
Record:
• MySQL 4.1 or later: via yiidbActiveRecord
• PostgreSQL 7.3 or later: via yiidbActiveRecord
• SQLite 2 and 3: via yiidbActiveRecord
• Microsoft SQL Server 2010 or later: via yiidbActiveRecord
• Oracle: via yiidbActiveRecord
• CUBRID 9.1 or later: via yiidbActiveRecord
• Sphnix: via yiisphinxActiveRecord, requires yii2-sphinx extension
• ElasticSearch: via yiielasticsearchActiveRecord, requires yii2-
elasticsearch extension
• Redis 2.6.12 or later: via yiiredisActiveRecord, requires yii2-
redis extension
13
http://en.wikipedia.org/wiki/Active_record_pattern
6.3. ACTIVE RECORD 227
• MongoDB 1.3.0 or later: via yiimongodbActiveRecord, requires
yii2-mongodb extension
As you can see, Yii provides Active Record support for relational databases
as well as NoSQL databases. In this tutorial, we will mainly describe the
usage of Active Record for relational databases. However, most content
described here are also applicable to Active Record for NoSQL databases.
6.3.1 Declaring Active Record Classes
To declare an Active Record class you need to extend yiidbActiveRecord
and implement the tableName method that returns the name of the database
table associated with the class:
namespace appmodels;
use yiidbActiveRecord;
class Customer extends ActiveRecord
{
/**
* @return string the name of the table associated with this
ActiveRecord class.
*/
public static function tableName()
{
return ’customer’;
}
}
6.3.2 Accessing Column Data
Active Record maps each column of the corresponding database table row
to an attribute in the Active Record object. An attribute behaves like a
regular object public property. The name of an attribute is the same as the
corresponding column name and is case-sensitive.
To read the value of a column, you can use the following syntax:
// "id" and "email" are the names of columns in the table associated with
$customer ActiveRecord object
$id = $customer->id;
$email = $customer->email;
To change the value of a column, assign a new value to the associated prop-
erty and save the object:
$customer->email = ’jane@example.com’;
$customer->save();
228 CHAPTER 6. WORKING WITH DATABASES
6.3.3 Connecting to Database
Active Record uses a yiidbConnection to exchange data with database.
By default, it uses the db application component as the connection. As
explained in Database basics, you may configure the db component in the
application configuration file like follows,
return [
’components’ => [
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=testdb’,
’username’ => ’demo’,
’password’ => ’demo’,
],
],
];
If you are using multiple databases in your application and you want to use
a different DB connection for your Active Record class, you may override the
yiidbActiveRecord::getDb() method:
class Customer extends ActiveRecord
{
// ...
public static function getDb()
{
return Yii::$app->db2; // use "db2" application component
}
}
6.3.4 Querying Data from Database
Active Record provides two entry methods for building DB queries and pop-
ulating data into Active Record instances:
• yiidbActiveRecord::find()
• yiidbActiveRecord::findBySql()
Both methods return an yiidbActiveQuery instance, which extends yii
dbQuery, and thus supports the same set of flexible and powerful DB
query building methods, such as where(), join(), orderBy(), etc. The following
examples demonstrate some of the possibilities.
// to retrieve all *active* customers and order them by their ID:
$customers = Customer::find()
->where([’status’ => Customer::STATUS_ACTIVE])
->orderBy(’id’)
->all();
6.3. ACTIVE RECORD 229
// to return a single customer whose ID is 1:
$customer = Customer::find()
->where([’id’ => 1])
->one();
// to return the number of *active* customers:
$count = Customer::find()
->where([’status’ => Customer::STATUS_ACTIVE])
->count();
// to index the result by customer IDs:
$customers = Customer::find()->indexBy(’id’)->all();
// $customers array is indexed by customer IDs
// to retrieve customers using a raw SQL statement:
$sql = ’SELECT * FROM customer’;
$customers = Customer::findBySql($sql)->all();
Tip: In the code above Customer::STATUS_ACTIVE is a constant
defined in Customer. It is a good practice to use meaningful con-
stant names rather than hardcoded strings or numbers in your
code.
Two shortcut methods are provided to return Active Record instances match-
ing a primary key value or a set of column values: findOne() and findAll().
The former returns the first matching instance while the latter returns all of
them. For example,
// to return a single customer whose ID is 1:
$customer = Customer::findOne(1);
// to return an *active* customer whose ID is 1:
$customer = Customer::findOne([
’id’ => 1,
’status’ => Customer::STATUS_ACTIVE,
]);
// to return customers whose ID is 1, 2 or 3:
$customers = Customer::findAll([1, 2, 3]);
// to return customers whose status is "deleted":
$customer = Customer::findAll([
’status’ => Customer::STATUS_DELETED,
]);
Retrieving Data in Arrays
Sometimes when you are processing a large amount of data, you may want
to use arrays to hold the data retrieved from database to save memory. This
can be done by calling asArray():
230 CHAPTER 6. WORKING WITH DATABASES
// to return customers in terms of arrays rather than ‘Customer‘ objects:
$customers = Customer::find()
->asArray()
->all();
// each element of $customers is an array of name-value pairs
Note that while this method saves memory and improves performance it is
a step to a lower abstraction layer and you will loose some features that
the active record layer has. Fetching data using asArray is nearly equal to
running a normal query using the query builder. When using asArray the
result will be returned just as such a query and no typecasting is performed
anymore so the result may contain string values for fields that are integer
when accessed on the active record object.
Retrieving Data in Batches
In Query Builder, we have explained that you may use batch query to keep
your memory usage under a limit when querying a large amount of data from
database. You may use the same technique in Active Record. For example,
// fetch 10 customers at a time
foreach (Customer::find()->batch(10) as $customers) {
// $customers is an array of 10 or fewer Customer objects
}
// fetch 10 customers at a time and iterate them one by one
foreach (Customer::find()->each(10) as $customer) {
// $customer is a Customer object
}
// batch query with eager loading
foreach (Customer::find()->with(’orders’)->each() as $customer) {
}
6.3.5 Manipulating Data in Database
Active Record provides the following methods to insert, update and delete
a single row in a table associated with a single Active Record instance:
• yiidbActiveRecord::save()
• yiidbActiveRecord::insert()
• yiidbActiveRecord::update()
• yiidbActiveRecord::delete()
Active Record also provides the following static methods that apply to a
whole table associated with an Active Record class. Be extremely careful
when using these methods as they affect the whole table. For example,
deleteAll() will delete ALL rows in the table.
6.3. ACTIVE RECORD 231
• yiidbActiveRecord::updateCounters()
• yiidbActiveRecord::updateAll()
• yiidbActiveRecord::updateAllCounters()
• yiidbActiveRecord::deleteAll()
The following examples show how to use these methods:
// to insert a new customer record
$customer = new Customer();
$customer->name = ’James’;
$customer->email = ’james@example.com’;
$customer->save(); // equivalent to $customer->insert();
// to update an existing customer record
$customer = Customer::findOne($id);
$customer->email = ’james@example.com’;
$customer->save(); // equivalent to $customer->update();
// to delete an existing customer record
$customer = Customer::findOne($id);
$customer->delete();
// to delete several customers
Customer::deleteAll(’age > :age AND gender = :gender’, [’:age’ => 20, ’:
gender’ => ’M’]);
// to increment the age of ALL customers by 1
Customer::updateAllCounters([’age’ => 1]);
Info: The save() method will call either insert() or update(),
depending on whether the Active Record instance is new or not
(internally it will check the value of yiidbActiveRecord::
isNewRecord). If an Active Record is instantiated via the new
operator, calling save() will insert a row in the table; calling
save() on active record fetched from database will update the
corresponding row in the table.
Data Input and Validation
Because Active Record extends from yiibaseModel, it supports the same
data input and validation features as described in Model. For example, you
may declare validation rules by overwriting the yiibaseModel::rules()
method; you may massively assign user input data to an Active Record
instance; and you may call yiibaseModel::validate() to trigger data
validation.
232 CHAPTER 6. WORKING WITH DATABASES
When you call save(), insert() or update(), these methods will automat-
ically call yiibaseModel::validate(). If the validation fails, the corres-
ponding data saving operation will be cancelled.
The following example shows how to use an Active Record to collect/val-
idate user input and save them into database:
// creating a new record
$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
// the user input has been collected, validated and saved
}
// updating a record whose primary key is $id
$model = Customer::findOne($id);
if ($model === null) {
throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
// the user input has been collected, validated and saved
}
Loading Default Values
Your table columns may be defined with default values. Sometimes, you may
want to pre-populate your Web form for an Active Record with these values.
To do so, call the loadDefaultValues() method before rendering the form:
$customer = new Customer();
$customer->loadDefaultValues();
// ... render HTML form for $customer ...
6.3.6 Active Record Life Cycles
It is important to understand the life cycles of Active Record when it is used
to manipulate data in database. These life cycles are typically associated
with corresponding events which allow you to inject code to intercept or
respond to these events. They are especially useful for developing Active
Record behaviors.
When instantiating a new Active Record instance, we will have the fol-
lowing life cycles:
1. constructor
2. yiidbActiveRecord::init(): will trigger an yiidbActiveRecord
::EVENT_INIT event
When querying data through the yiidbActiveRecord::find() method,
we will have the following life cycles for EVERY newly populated Active
Record instance:
6.3. ACTIVE RECORD 233
1. constructor
2. yiidbActiveRecord::init(): will trigger an yiidbActiveRecord
::EVENT_INIT event
3. yiidbActiveRecord::afterFind(): will trigger an yiidbActiveRecord
::EVENT_AFTER_FIND event
When calling yiidbActiveRecord::save() to insert or update an Act-
iveRecord, we will have the following life cycles:
1. yiidbActiveRecord::beforeValidate(): will trigger an yiidb
ActiveRecord::EVENT_BEFORE_VALIDATE event
2. yiidbActiveRecord::afterValidate(): will trigger an yiidbActiveRecord
::EVENT_AFTER_VALIDATE event
3. yiidbActiveRecord::beforeSave(): will trigger an yiidbActiveRecord
::EVENT_BEFORE_INSERT or yiidbActiveRecord::EVENT_BEFORE_UPDATE
event
4. perform the actual data insertion or updating
5. yiidbActiveRecord::afterSave(): will trigger an yiidbActiveRecord
::EVENT_AFTER_INSERT or yiidbActiveRecord::EVENT_AFTER_UPDATE
event
And Finally when calling yiidbActiveRecord::delete() to delete an
ActiveRecord, we will have the following life cycles:
1. yiidbActiveRecord::beforeDelete(): will trigger an yiidbActiveRecord
::EVENT_BEFORE_DELETE event
2. perform the actual data deletion
3. yiidbActiveRecord::afterDelete(): will trigger an yiidbActiveRecord
::EVENT_AFTER_DELETE event
6.3.7 Working with Relational Data
You can use ActiveRecord to also query a table’s relational data (i.e., se-
lection of data from Table A can also pull in related data from Table B).
Thanks to ActiveRecord, the relational data returned can be accessed like a
property of the ActiveRecord object associated with the primary table.
For example, with an appropriate relation declaration, by accessing $customer
->orders you may obtain an array of Order objects which represent the orders
placed by the specified customer.
234 CHAPTER 6. WORKING WITH DATABASES
To declare a relation, define a getter method which returns an yiidb
ActiveQuery object that has relation information about the relation con-
text and thus will only query for related records. For example,
class Customer extends yiidbActiveRecord
{
public function getOrders()
{
// Customer has_many Order via Order.customer_id -> id
return $this->hasMany(Order::className(), [’customer_id’ => ’id’]);
}
}
class Order extends yiidbActiveRecord
{
public function getCustomer()
{
// Order has_one Customer via Customer.id -> customer_id
return $this->hasOne(Customer::className(), [’id’ => ’customer_id’])
;
}
}
The methods yiidbActiveRecord::hasMany() and yiidbActiveRecord
::hasOne() used in the above are used to model the many-one relationship
and one-one relationship in a relational database. For example, a customer
has many orders, and an order has one customer. Both methods take two
parameters and return an yiidbActiveQuery object:
• $class: the name of the class of the related model(s). This should be
a fully qualified class name.
• $link: the association between columns from the two tables. This
should be given as an array. The keys of the array are the names of
the columns from the table associated with $class, while the values of
the array are the names of the columns from the declaring class. It is
a good practice to define relationships based on table foreign keys.
After declaring relations, getting relational data is as easy as accessing a
component property that is defined by the corresponding getter method:
// get the orders of a customer
$customer = Customer::findOne(1);
$orders = $customer->orders; // $orders is an array of Order objects
Behind the scene, the above code executes the following two SQL queries,
one for each line of code:
SELECT * FROM customer WHERE id=1;
SELECT * FROM order WHERE customer_id=1;
6.3. ACTIVE RECORD 235
Tip: If you access the expression $customer->orders again, it will
not perform the second SQL query again. The SQL query is only
performed the first time when this expression is accessed. Any
further accesses will only return the previously fetched results
that are cached internally. If you want to re-query the relational
data, simply unset the existing one first: unset($customer->orders
);.
Sometimes, you may want to pass parameters to a relational query. For
example, instead of returning all orders of a customer, you may want to
return only big orders whose subtotal exceeds a specified amount. To do so,
declare a bigOrders relation with the following getter method:
class Customer extends yiidbActiveRecord
{
public function getBigOrders($threshold = 100)
{
return $this->hasMany(Order::className(), [’customer_id’ => ’id’])
->where(’subtotal > :threshold’, [’:threshold’ => $threshold])
->orderBy(’id’);
}
}
Remember that hasMany() returns an yiidbActiveQuery object which al-
lows you to customize the query by calling the methods of yiidbActiveQuery.
With the above declaration, if you access $customer->bigOrders, it will only
return the orders whose subtotal is greater than 100. To specify a different
threshold value, use the following code:
$orders = $customer->getBigOrders(200)->all();
Note: A relation method returns an instance of yiidbActiveQuery.
If you access the relation like an attribute (i.e. a class prop-
erty), the return value will be the query result of the relation,
which could be an instance of yiidbActiveRecord, an array
of that, or null, depending the multiplicity of the relation. For
example, $customer->getOrders() returns an ActiveQuery instance,
while $customer->orders returns an array of Order objects (or an
empty array if the query results in nothing).
6.3.8 Relations with Pivot Table
Sometimes, two tables are related together via an intermediary table called
pivot table14. To declare such relations, we can customize the yiidb
ActiveQuery object by calling its yiidbActiveQuery::via() or yiidb
ActiveQuery::viaTable() method.
14
Pivot table on Wikipedia: http://en.wikipedia.org/wiki/Pivot_table
236 CHAPTER 6. WORKING WITH DATABASES
For example, if table order and table item are related via pivot table
order_item, we can declare the items relation in the Order class like the fol-
lowing:
class Order extends yiidbActiveRecord
{
public function getItems()
{
return $this->hasMany(Item::className(), [’id’ => ’item_id’])
->viaTable(’order_item’, [’order_id’ => ’id’]);
}
}
The yiidbActiveQuery::via() method is similar to yiidbActiveQuery
::viaTable() except that the first parameter of yiidbActiveQuery::
via() takes a relation name declared in the ActiveRecord class instead of
the pivot table name. For example, the above items relation can be equival-
ently declared as follows:
class Order extends yiidbActiveRecord
{
public function getOrderItems()
{
return $this->hasMany(OrderItem::className(), [’order_id’ => ’id’]);
}
public function getItems()
{
return $this->hasMany(Item::className(), [’id’ => ’item_id’])
->via(’orderItems’);
}
}
6.3.9 Lazy and Eager Loading
As described earlier, when you access the related objects the first time, Act-
iveRecord will perform a DB query to retrieve the corresponding data and
populate it into the related objects. No query will be performed if you access
the same related objects again. We call this lazy loading. For example,
// SQL executed: SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1
$orders = $customer->orders;
// no SQL executed
$orders2 = $customer->orders;
Lazy loading is very convenient to use. However, it may suffer from a per-
formance issue in the following scenario:
// SQL executed: SELECT * FROM customer LIMIT 100
$customers = Customer::find()->limit(100)->all();
6.3. ACTIVE RECORD 237
foreach ($customers as $customer) {
// SQL executed: SELECT * FROM order WHERE customer_id=...
$orders = $customer->orders;
// ...handle $orders...
}
How many SQL queries will be performed in the above code, assuming there
are more than 100 customers in the database? 101! The first SQL query
brings back 100 customers. Then for each customer, a SQL query is per-
formed to bring back the orders of that customer.
To solve the above performance problem, you can use the so-called eager
loading approach by calling yiidbActiveQuery::with():
// SQL executed: SELECT * FROM customer LIMIT 100;
// SELECT * FROM orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)
->with(’orders’)->all();
foreach ($customers as $customer) {
// no SQL executed
$orders = $customer->orders;
// ...handle $orders...
}
As you can see, only two SQL queries are needed for the same task!
Info: In general, if you are eager loading N relations among which
M relations are defined with via() or viaTable(), a total number
of 1+M+N SQL queries will be performed: one query to bring back
the rows for the primary table, one for each of the M pivot tables
corresponding to the via() or viaTable() calls, and one for each
of the N related tables.
Note: When you are customizing select() with eager loading,
make sure you include the columns that link the related models.
Otherwise, the related models will not be loaded. For example,
$orders = Order::find()->select([’id’, ’amount’])->with(’customer’)->all();
// $orders[0]->customer is always null. To fix the problem, you should do
the following:
$orders = Order::find()->select([’id’, ’amount’, ’customer_id’])->with(’
customer’)->all();
Sometimes, you may want to customize the relational queries on the fly. This
can be done for both lazy loading and eager loading. For example,
$customer = Customer::findOne(1);
// lazy loading: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
$orders = $customer->getOrders()->where(’subtotal>100’)->all();
// eager loading: SELECT * FROM customer LIMIT 100
238 CHAPTER 6. WORKING WITH DATABASES
// SELECT * FROM order WHERE customer_id IN (1,2,...) AND
subtotal>100
$customers = Customer::find()->limit(100)->with([
’orders’ => function($query) {
$query->andWhere(’subtotal>100’);
},
])->all();
6.3.10 Inverse Relations
Relations can often be defined in pairs. For example, Customer may have a
relation named orders while Order may have a relation named customer:
class Customer extends ActiveRecord
{
....
public function getOrders()
{
return $this->hasMany(Order::className(), [’customer_id’ => ’id’]);
}
}
class Order extends ActiveRecord
{
....
public function getCustomer()
{
return $this->hasOne(Customer::className(), [’id’ => ’customer_id’])
;
}
}
If we perform the following query, we would find that the customer of an
order is not the same customer object that finds those orders, and accessing
customer->orders will trigger one SQL execution while accessing the customer
of an order will trigger another SQL execution:
// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// echoes "not equal"
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
echo ’equal’;
} else {
echo ’not equal’;
}
To avoid the redundant execution of the last SQL statement, we could declare
the inverse relations for the customer and the orders relations by calling the
yiidbActiveQuery::inverseOf() method, like the following:
class Customer extends ActiveRecord
6.3. ACTIVE RECORD 239
{
....
public function getOrders()
{
return $this->hasMany(Order::className(), [’customer_id’ => ’id’])->
inverseOf(’customer’);
}
}
Now if we execute the same query as shown above, we would get:
// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// echoes "equal"
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
echo ’equal’;
} else {
echo ’not equal’;
}
In the above, we have shown how to use inverse relations in lazy loading.
Inverse relations also apply in eager loading:
// SELECT * FROM customer
// SELECT * FROM order WHERE customer_id IN (1, 2, ...)
$customers = Customer::find()->with(’orders’)->all();
// echoes "equal"
if ($customers[0]->orders[0]->customer === $customers[0]) {
echo ’equal’;
} else {
echo ’not equal’;
}
Note: Inverse relation cannot be defined with a relation that in-
volves pivoting tables. That is, if your relation is defined with yii
dbActiveQuery::via() or yiidbActiveQuery::viaTable(),
you cannot call yiidbActiveQuery::inverseOf() further.
6.3.11 Joining with Relations
When working with relational databases, a common task is to join multiple
tables and apply various query conditions and parameters to the JOIN SQL
statement. Instead of calling yiidbActiveQuery::join() explicitly to
build up the JOIN query, you may reuse the existing relation definitions and
call yiidbActiveQuery::joinWith() to achieve this goal. For example,
// find all orders and sort the orders by the customer id and the order id.
also eager loading "customer"
$orders = Order::find()->joinWith(’customer’)->orderBy(’customer.id, order.
id’)->all();
// find all orders that contain books, and eager loading "books"
$orders = Order::find()->innerJoinWith(’books’)->all();
240 CHAPTER 6. WORKING WITH DATABASES
In the above, the method yiidbActiveQuery::innerJoinWith() is a short-
cut to yiidbActiveQuery::joinWith() with the join type set as INNER
JOIN.
You may join with one or multiple relations; you may apply query con-
ditions to the relations on-the-fly; and you may also join with sub-relations.
For example,
// join with multiple relations
// find out the orders that contain books and are placed by customers who
registered within the past 24 hours
$orders = Order::find()->innerJoinWith([
’books’,
’customer’ => function ($query) {
$query->where(’customer.created_at > ’ . (time() - 24 * 3600));
}
])->all();
// join with sub-relations: join with books and books’ authors
$orders = Order::find()->joinWith(’books.author’)->all();
Behind the scene, Yii will first execute a JOIN SQL statement to bring back
the primary models satisfying the conditions applied to the JOIN SQL. It
will then execute a query for each relation and populate the corresponding
related records.
The difference between yiidbActiveQuery::joinWith() and yiidb
ActiveQuery::with() is that the former joins the tables for the primary
model class and the related model classes to retrieve the primary models,
while the latter just queries against the table for the primary model class to
retrieve the primary models.
Because of this difference, you may apply query conditions that are only
available to a JOIN SQL statement. For example, you may filter the primary
models by the conditions on the related models, like the example above. You
may also sort the primary models using columns from the related tables.
When using yiidbActiveQuery::joinWith(), you are responsible to
disambiguate column names. In the above examples, we use item.id and
order.id to disambiguate the id column references because both of the order
table and the item table contain a column named id.
By default, when you join with a relation, the relation will also be eagerly
loaded. You may change this behavior by passing the $eagerLoading para-
meter which specifies whether to eager load the specified relations.
And also by default, yiidbActiveQuery::joinWith() uses LEFT JOIN
to join the related tables. You may pass it with the $joinType parameter to
customize the join type. As a shortcut to the INNER JOIN type, you may use
yiidbActiveQuery::innerJoinWith().
Below are some more examples,
// find all orders that contain books, but do not eager loading "books".
$orders = Order::find()->innerJoinWith(’books’, false)->all();
// which is equivalent to the above
6.3. ACTIVE RECORD 241
$orders = Order::find()->joinWith(’books’, false, ’INNER JOIN’)->all();
Sometimes when joining two tables, you may need to specify some extra
condition in the ON part of the JOIN query. This can be done by calling
the yiidbActiveQuery::onCondition() method like the following:
class User extends ActiveRecord
{
public function getBooks()
{
return $this->hasMany(Item::className(), [’owner_id’ => ’id’])->
onCondition([’category_id’ => 1]);
}
}
In the above, the yiidbActiveRecord::hasMany() method returns an yii
dbActiveQuery instance, upon which yiidbActiveQuery::onCondition()
is called to specify that only items whose category_id is 1 should be returned.
When you perform query using yiidbActiveQuery::joinWith(), the
on-condition will be put in the ON part of the corresponding JOIN query.
For example,
// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND
category_id=1
// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1
$users = User::find()->joinWith(’books’)->all();
Note that if you use eager loading via yiidbActiveQuery::with() or lazy
loading, the on-condition will be put in the WHERE part of the correspond-
ing SQL statement, because there is no JOIN query involved. For example,
// SELECT * FROM user WHERE id=10
$user = User::findOne(10);
// SELECT * FROM item WHERE owner_id=10 AND category_id=1
$books = $user->books;
6.3.12 Working with Relationships
ActiveRecord provides the following two methods for establishing and break-
ing a relationship between two ActiveRecord objects:
• yiidbActiveRecord::link()
• yiidbActiveRecord::unlink()
For example, given a customer and a new order, we can use the following
code to make the order owned by the customer:
$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link(’orders’, $order);
242 CHAPTER 6. WORKING WITH DATABASES
The yiidbActiveRecord::link() call above will set the customer_id of
the order to be the primary key value of $customer and then call yiidb
ActiveRecord::save() to save the order into database.
6.3.13 Cross-DBMS Relations
ActiveRecord allows to establish relationship between entities from differ-
ent DBMS. For example: between relational database table and MongoDB
collection. Such relation does not require any special code:
// Relational database Active Record
class Customer extends yiidbActiveRecord
{
public static function tableName()
{
return ’customer’;
}
public function getComments()
{
// Customer, stored in relational database, has many Comments,
stored in MongoDB collection:
return $this->hasMany(Comment::className(), [’customer_id’ => ’id’])
;
}
}
// MongoDb Active Record
class Comment extends yiimongodbActiveRecord
{
public static function collectionName()
{
return ’comment’;
}
public function getCustomer()
{
// Comment, stored in MongoDB collection, has one Customer, stored
in relational database:
return $this->hasOne(Customer::className(), [’id’ => ’customer_id’])
;
}
}
All Active Record features like eager and lazy loading, establishing and
breaking a relationship and so on, are available for cross-DBMS relations.
Note: do not forget Active Record solutions for different DBMS
may have specific methods and features, which may not be ap-
plied for cross-DBMS relations. For example: usage of yiidb
ActiveQuery::joinWith() will obviously not work with rela-
tion to the MongoDB collection.
6.3. ACTIVE RECORD 243
6.3.14 Scopes
When you call yiidbActiveRecord::find() or yiidbActiveRecord::
findBySql(), it returns an yiidbActiveQuery instance. You may call
additional query methods, such as yiidbActiveQuery::where(), yiidb
ActiveQuery::orderBy(), to further specify the query conditions.
It is possible that you may want to call the same set of query methods in
different places. If this is the case, you should consider defining the so-called
scopes. A scope is essentially a method defined in a custom query class that
calls a set of query methods to modify the query object. You can then use
a scope like calling a normal query method.
Two steps are required to define a scope. First create a custom query
class for your model and define the needed scope methods in this class. For
example, create a CommentQuery class for the Comment model and define the
active() scope method like the following:
namespace appmodels;
use yiidbActiveQuery;
class CommentQuery extends ActiveQuery
{
public function active($state = true)
{
$this->andWhere([’active’ => $state]);
return $this;
}
}
Important points are:
1. Class should extend from yiidbActiveQuery (or another ActiveQuery
such as yiimongodbActiveQuery).
2. A method should be public and should return $this in order to allow
method chaining. It may accept parameters.
3. Check yiidbActiveQuery methods that are very useful for modify-
ing query conditions.
Second, override yiidbActiveRecord::find() to use the custom query
class instead of the regular yiidbActiveQuery. For the example above,
you need to write the following code:
namespace appmodels;
use yiidbActiveRecord;
class Comment extends ActiveRecord
{
/**
244 CHAPTER 6. WORKING WITH DATABASES
* @inheritdoc
* @return CommentQuery
*/
public static function find()
{
return new CommentQuery(get_called_class());
}
}
That’s it. Now you can use your custom scope methods:
$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();
You can also use scopes when defining relations. For example,
class Post extends yiidbActiveRecord
{
public function getActiveComments()
{
return $this->hasMany(Comment::className(), [’post_id’ => ’id’])->
active();
}
}
Or use the scopes on-the-fly when performing relational query:
$posts = Post::find()->with([
’comments’ => function($q) {
$q->active();
}
])->all();
Default Scope
If you used Yii 1.1 before, you may know a concept called default scope. A
default scope is a scope that applies to ALL queries. You can define a default
scope easily by overriding yiidbActiveRecord::find(). For example,
public static function find()
{
return parent::find()->where([’deleted’ => false]);
}
Note that all your queries should then not use yiidbActiveQuery::where()
but yiidbActiveQuery::andWhere() and yiidbActiveQuery::orWhere()
to not override the default condition.
6.3.15 Transactional operations
There are two ways of dealing with transactions while working with Active
Record. First way is doing everything manually as described in “transac-
tions” section of “Database basics“. Another way is to do it by implement-
6.3. ACTIVE RECORD 245
ing transactions method where you can specify which operations are to be
wrapped into transaction per model scenario:
class Post extends yiidbActiveRecord
{
public function transactions()
{
return [
’admin’ => self::OP_INSERT,
’api’ => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
// the above is equivalent to the following:
// ’api’ => self::OP_ALL,
];
}
}
In the above admin and api are model scenarios and constants starting with
OP_ are operations that should be wrapped in transaction for these sceanarios.
Supported operations are OP_INSERT, OP_UPDATE and OP_DELETE. OP_ALL stands
for all three.
Such automatic transactions are especially useful if you’re doing addi-
tional database changes in beforeSave, afterSave, beforeDelete, afterDelete
and want to be sure that both succeeded before they are saved.
6.3.16 Optimistic Locks
Optimistic locking allows multiple users to access the same record for edits
and avoids potential conflicts. In case when a user attempts to save the
record upon some staled data (because another user has modified the data),
a yiidbStaleObjectException exception will be thrown, and the update
or deletion is skipped.
Optimistic locking is only supported by update() and delete() methods
and isn’t used by default.
To use Optimistic locking:
1. Create a column to store the version number of each row. The column
type should be BIGINT DEFAULT 0. Override optimisticLock() method to
return the name of this column.
2. In the Web form that collects the user input, add a hidden field that
stores the lock version of the recording being updated.
3. In the controller action that does the data updating, try to catch
the yiidbStaleObjectException and implement necessary busi-
ness logic (e.g. merging the changes, prompting stated data) to resolve
the conflict.
246 CHAPTER 6. WORKING WITH DATABASES
6.3.17 Dirty Attributes
An attribute is considered dirty if its value was modified since model was
loaded from database or since most recent data save. When saving record
data by calling save(), update(), insert() etc. only dirty attributes are saved
into database. If there are no dirty attributes there’s nothing to be saved so
no query will be issued at all.
6.3.18 See also
• Model
• yiidbActiveRecord
6.4 Database Migration
Note: This section is under development.
Like source code, the structure of a database evolves as a database-driven
application is developed and maintained. For example, during development,
a new table may be added; Or, after the application goes live, it may be
discovered that an additional index is required. It is important to keep track
of these structural database changes (called migration), just as changes to
the source code is tracked using version control. If the source code and the
database become out of sync, bugs will occur, or the whole application might
break. For this reason, Yii provides a database migration tool that can keep
track of database migration history, apply new migrations, or revert existing
ones.
The following steps show how database migration is used by a team
during development:
1. Tim creates a new migration (e.g. creates a new table, changes a
column definition, etc.).
2. Tim commits the new migration into the source control system (e.g.
Git, Mercurial).
3. Doug updates his repository from the source control system and re-
ceives the new migration.
4. Doug applies the migration to his local development database, thereby
syncing his database to reflect the changes Tim made.
Yii supports database migration via the yii migrate command line tool. This
tool supports:
6.4. DATABASE MIGRATION 247
• Creating new migrations
• Applying, reverting, and redoing migrations
• Showing migration history and new migrations
6.4.1 Creating Migrations
To create a new migration, run the following command:
yii migrate/create <name>
The required name parameter specifies a very brief description of the migra-
tion. For example, if the migration creates a new table named news, you’d
use the command:
yii migrate/create create_news_table
As you’ll shortly see, the name parameter is used as part of a PHP class name
in the migration. Therefore, it should only contain letters, digits and/or
underscore characters.
The above command will create a new file named m101129_185401_create_news_table
.php. This file will be created within the @app/migrations directory. Initially,
the migration file will be generated with the following code:
class m101129_185401_create_news_table extends yiidbMigration
{
public function up()
{
}
public function down()
{
echo "m101129_185401_create_news_table cannot be reverted.n";
return false;
}
}
Notice that the class name is the same as the file name, and follows the
pattern m<timestamp>_<name>, where:
• <timestamp> refers to the UTC timestamp (in the format of yymmdd_hhmmss
) when the migration is created,
• <name> is taken from the command’s name parameter.
In the class, the up() method should contain the code implementing the
actual database migration. In other words, the up() method executes code
that actually changes the database. The down() method may contain code
that reverts the changes made by up().
Sometimes, it is impossible for the down() to undo the database migration.
For example, if the migration deletes table rows or an entire table, that data
248 CHAPTER 6. WORKING WITH DATABASES
cannot be recovered in the down() method. In such cases, the migration is
called irreversible, meaning the database cannot be rolled back to a previous
state. When a migration is irreversible, as in the above generated code,
the down() method returns false to indicate that the migration cannot be
reverted.
As an example, let’s show the migration about creating a news table.
use yiidbSchema;
class m101129_185401_create_news_table extends yiidbMigration
{
public function up()
{
$this->createTable(’news’, [
’id’ => ’pk’,
’title’ => Schema::TYPE_STRING . ’ NOT NULL’,
’content’ => Schema::TYPE_TEXT,
]);
}
public function down()
{
$this->dropTable(’news’);
}
}
The base class yiidbMigration exposes a database connection via db
property. You can use it for manipulating data and schema of a database.
The column types used in this example are abstract types that will be
replaced by Yii with the corresponding types depended on your database
management system. You can use them to write database independent mi-
grations. For example pk will be replaced by int(11) NOT NULL AUTO_INCREMENT
PRIMARY KEY for MySQL and integer PRIMARY KEY AUTOINCREMENT NOT NULL for
sqlite. See documentation of yiidbQueryBuilder::getColumnType() for
more details and a list of available types. You may also use the constants
defined in yiidbSchema to define column types.
Note: You can add constraints and other custom table options
at the end of the table description by specifying them as simple
string. For example in the above migration, after content at-
tribute definition you can write ’CONSTRAINT ...’ or other custom
options.
6.4.2 Transactional Migrations
While performing complex DB migrations, we usually want to make sure that
each migration succeed or fail as a whole so that the database maintains the
6.4. DATABASE MIGRATION 249
consistency and integrity. In order to achieve this goal, we can exploit DB
transactions. We could use special methods safeUp and safeDown for these
purposes.
use yiidbSchema;
class m101129_185401_create_news_table extends yiidbMigration
{
public function safeUp()
{
$this->createTable(’news’, [
’id’ => ’pk’,
’title’ => Schema::TYPE_STRING . ’ NOT NULL’,
’content’ => Schema::TYPE_TEXT,
]);
$this->createTable(’user’, [
’id’ => ’pk’,
’login’ => Schema::TYPE_STRING . ’ NOT NULL’,
’password’ => Schema::TYPE_STRING . ’ NOT NULL’,
]);
}
public function safeDown()
{
$this->dropTable(’news’);
$this->dropTable(’user’);
}
}
When your code uses more then one query it is recommended to use safeUp
and safeDown.
Note: Not all DBMS support transactions. And some DB queries
cannot be put into a transaction. In this case, you will have to
implement up() and down(), instead. And for MySQL, some SQL
statements may cause implicit commit15.
6.4.3 Applying Migrations
To apply all available new migrations (i.e., make the local database up-to-
date), run the following command:
yii migrate
The command will show the list of all new migrations. If you confirm to
apply the migrations, it will run the up() method in every new migration
class, one after another, in the order of the timestamp value in the class
name.
15
http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html
250 CHAPTER 6. WORKING WITH DATABASES
After applying a migration, the migration tool will keep a record in a
database table named migration. This allows the tool to identify which mi-
grations have been applied and which are not. If the migration table does
not exist, the tool will automatically create it in the database specified by
the db application component.
Sometimes, we may only want to apply one or a few new migrations. We
can use the following command:
yii migrate/up 3
This command will apply the 3 new migrations. Changing the value 3 will
allow us to change the number of migrations to be applied.
We can also migrate the database to a specific version with the following
command:
yii migrate/to 101129_185401
That is, we use the timestamp part of a migration name to specify the version
that we want to migrate the database to. If there are multiple migrations
between the last applied migration and the specified migration, all these
migrations will be applied. If the specified migration has been applied before,
then all migrations applied after it will be reverted (to be described in the
next section).
6.4.4 Reverting Migrations
To revert the last one or several applied migrations, we can use the following
command:
yii migrate/down [step]
where the optional step parameter specifies how many migrations to be rever-
ted back. It defaults to 1, meaning reverting back the last applied migration.
As we described before, not all migrations can be reverted. Trying to
revert such migrations will throw an exception and stop the whole reverting
process.
6.4.5 Redoing Migrations
Redoing migrations means first reverting and then applying the specified
migrations. This can be done with the following command:
yii migrate/redo [step]
where the optional step parameter specifies how many migrations to be re-
done. It defaults to 1, meaning redoing the last migration.
6.4. DATABASE MIGRATION 251
6.4.6 Showing Migration Information
Besides applying and reverting migrations, the migration tool can also dis-
play the migration history and the new migrations to be applied.
yii migrate/history [limit]
yii migrate/new [limit]
where the optional parameter limit specifies the number of migrations to be
displayed. If limit is not specified, all available migrations will be displayed.
The first command shows the migrations that have been applied, while
the second command shows the migrations that have not been applied.
6.4.7 Modifying Migration History
Sometimes, we may want to modify the migration history to a specific migra-
tion version without actually applying or reverting the relevant migrations.
This often happens when developing a new migration. We can use the fol-
lowing command to achieve this goal.
yii migrate/mark 101129_185401
This command is very similar to yii migrate/to command, except that it
only modifies the migration history table to the specified version without
applying or reverting the migrations.
6.4.8 Customizing Migration Command
There are several ways to customize the migration command.
Use Command Line Options
The migration command comes with four options that can be specified in
command line:
• interactive: boolean, specifies whether to perform migrations in an
interactive mode. Defaults to true, meaning the user will be prompted
when performing a specific migration. You may set this to false should
the migrations be done in a background process.
• migrationPath: string, specifies the directory storing all migration class
files. This must be specified in terms of a path alias, and the corres-
ponding directory must exist. If not specified, it will use the migrations
sub-directory under the application base path.
• migrationTable: string, specifies the name of the database table for
storing migration history information. It defaults to migration. The
table structure is version varchar(255) primary key, apply_time integer.
252 CHAPTER 6. WORKING WITH DATABASES
• db: string, specifies the ID of the database application component.
Defaults to ‘db’.
• templateFile: string, specifies the path of the file to be served as the
code template for generating the migration classes. This must be spe-
cified in terms of a path alias (e.g. application.migrations.template).
If not set, an internal template will be used. Inside the template, the
token {ClassName} will be replaced with the actual migration class name.
To specify these options, execute the migrate command using the following
format
yii migrate/up --option1=value1 --option2=value2 ...
For example, if we want to migrate for a forum module whose migration files
are located within the module’s migrations directory, we can use the following
command:
yii migrate/up --migrationPath=@app/modules/forum/migrations
Configure Command Globally
While command line options allow us to configure the migration command
on-the-fly, sometimes we may want to configure the command once for all.
For example, we may want to use a different table to store the migration
history, or we may want to use a customized migration template. We can do
so by modifying the console application’s configuration file like the following,
’controllerMap’ => [
’migrate’ => [
’class’ => ’yiiconsolecontrollersMigrateController’,
’migrationTable’ => ’my_custom_migrate_table’,
],
]
Now if we run the migrate command, the above configurations will take effect
without requiring us to enter the command line options every time. Other
command options can be also configured this way.
Migrating with Multiple Databases
By default, migrations will be applied to the database specified by the db
application component. You may change it by specifying the --db option, for
example,
yii migrate --db=db2
The above command will apply all migrations found in the default migration
path to the db2 database.
If your application works with multiple databases, it is possible that some
migrations should be applied to one database while some others should be
6.4. DATABASE MIGRATION 253
applied to another database. In this case, it is recommended that you create
a base migration class for each different database and override the yiidb
Migration::init() method like the following,
public function init()
{
$this->db = ’db2’;
parent::init();
}
To create a migration that should be applied to a particular database, simply
extend from the corresponding base migration class. Now if you run the
yii migrate command, each migration will be applied to its corresponding
database.
Info: Because each migration uses hardcoded DB connection,
the --db option of the migrate command will have no effect. Also
note that the migration history will be stored in the default db
database.
If you want to support changing DB connection via the --db option, you may
take the following alternative approach to work with multiple databases.
For each database, create a migration path and save all corresponding
migration classes there. To apply migrations, run the command as follows,
yii migrate --migrationPath=@app/migrations/db1 --db=db1
yii migrate --migrationPath=@app/migrations/db2 --db=db2
...
Info: The above approach stores the migration history in different
databases specified via the --db option.
254 CHAPTER 6. WORKING WITH DATABASES
Error: not existing file: db-sphinx.md
6.4. DATABASE MIGRATION 255
Error: not existing file: db-redis.md
256 CHAPTER 6. WORKING WITH DATABASES
Error: not existing file: db-mongodb.md
6.4. DATABASE MIGRATION 257
Error: not existing file: db-elasticsearch.md
258 CHAPTER 6. WORKING WITH DATABASES
Chapter 7
Getting Data from Users
7.1 Working with Forms
Note: This section is under development.
The primary way of using forms in Yii is through yiiwidgetsActiveForm.
This approach should be preferred when the form is based upon a model.
Additionally, there are some useful methods in yiihelpersHtml that are
typically used for adding buttons and help text to any form.
When creating model-based forms, the first step is to define the model
itself. The model can be either based upon the Active Record class, or the
more generic Model class. For this login example, a generic model will be
used:
use yiibaseModel;
class LoginForm extends Model
{
public $username;
public $password;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
// username and password are both required
[[’username’, ’password’], ’required’],
// password is validated by validatePassword()
[’password’, ’validatePassword’],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
259
260 CHAPTER 7. GETTING DATA FROM USERS
*/
public function validatePassword()
{
$user = User::findByUsername($this->username);
if (!$user || !$user->validatePassword($this->password)) {
$this->addError(’password’, ’Incorrect username or password.’);
}
}
/**
* Logs in a user using the provided username and password.
* @return boolean whether the user is logged in successfully
*/
public function login()
{
if ($this->validate()) {
$user = User::findByUsername($this->username);
return true;
} else {
return false;
}
}
}
The controller will pass an instance of that model to the view, wherein the
yiiwidgetsActiveForm widget is used:
use yiihelpersHtml;
use yiiwidgetsActiveForm;
<?php $form = ActiveForm::begin([
’id’ => ’login-form’,
’options’ => [’class’ => ’form-horizontal’],
]) ?>
<?= $form->field($model, ’username’) ?>
<?= $form->field($model, ’password’)->passwordInput() ?>
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<?= Html::submitButton(’Login’, [’class’ => ’btn btn-primary’])
?>
</div>
</div>
<?php ActiveForm::end() ?>
In the above code, yiiwidgetsActiveForm::begin() not only creates a
form instance, but also marks the beginning of the form. All of the con-
tent placed between yiiwidgetsActiveForm::begin() and yiiwidgets
ActiveForm::end() will be wrapped within the <form> tag. As with any
widget, you can specify some options as to how the widget should be con-
figured by passing an array to the begin method. In this case, an extra CSS
class and identifying ID are passed to be used in the opening <form> tag.
7.1. WORKING WITH FORMS 261
In order to create a form element in the form, along with the element’s la-
bel, and any application JavaScript validation, the yiiwidgetsActiveForm
::field() method of the Active Form widget is called. When the invoca-
tion of this method is echoed directly, the result is a regular (text) input. To
customize the output, you can chain additional methods to this call:
<?= $form->field($model, ’password’)->passwordInput() ?>
// or
<?= $form->field($model, ’username’)->textInput()->hint(’Please enter your
name’)->label(’Name’) ?>
This will create all the <label>, <input> and other tags according to the
template defined by the form field. To add these tags yourself you can use
the Html helper class.
If you want to use one of HTML5 fields you may specify input type
directly like the following:
<?= $form->field($model, ’email’)->input(’email’) ?>
Specifying the attribute of the model can be done in more sophisticated ways.
For example when an attribute may take an array value when uploading
multiple files or selecting multiple items you may specify it by appending []
to the attribute name:
// allow multiple files to be uploaded:
echo $form->field($model, ’uploadFile[]’)->fileInput([’multiple’=>’multiple’
]);
// allow multiple items to be checked:
echo $form->field($model, ’items[]’)->checkboxList([’a’ => ’Item A’, ’b’ =>
’Item B’, ’c’ => ’Item C’]);
Tip: in order to style required fields with asterisk you can use
the following CSS:
div.required label:after {
content: " *";
color: red;
}
7.1.1 Handling multiple models with a single form
Sometimes you need to handle multiple models of the same kind in a single
form. For example, multiple settings where each setting is stored as name-
value and is represented by Setting model. The following shows how to
implement it with Yii.
Let’s start with controller action:
262 CHAPTER 7. GETTING DATA FROM USERS
namespace appcontrollers;
use Yii;
use yiibaseModel;
use yiiwebController;
use appmodelsSetting;
class SettingsController extends Controller
{
// ...
public function actionUpdate()
{
$settings = Setting::find()->indexBy(’id’)->all();
if (Model::loadMultiple($settings, Yii::$app->request->post()) &&
Model::validateMultiple($settings)) {
foreach ($settings as $setting) {
$setting->save(false);
}
return $this->redirect(’index’);
}
return $this->render(’update’, [’settings’ => $settings]);
}
}
In the code above we’re using indexBy when retrieving models from database
to make array indexed by model ids. These will be later used to identify
form fields. loadMultiple fills multiple modelds with the form data coming
from POST and validateMultiple validates all models at once. In order to
skip validation when saving we’re passing false as a parameter to save.
Now the form that’s in update view:
<?php
use yiihelpersHtml;
use yiiwidgetsActiveForm;
$form = ActiveForm::begin();
foreach ($settings as $index => $setting) {
echo Html::encode($setting->name) . ’: ’ . $form->field($setting, "[
$index]value");
}
ActiveForm::end();
Here for each setting we are rendering name and an input with a value. It is
important to add a proper index to input name since that is how loadMultiple
determines which model to fill with which values.
7.2. VALIDATING INPUT 263
7.2 Validating Input
As a rule of thumb, you should never trust the data received from end users
and should always validate them before putting them to good use.
Given a model populated with user inputs, you can validate the inputs by
calling the yiibaseModel::validate() method. The method will return
a boolean value indicating whether the validation succeeds or not. If not,
you may get the error messages from the yiibaseModel::errors property.
For example,
$model = new appmodelsContactForm;
// populate model attributes with user inputs
$model->attributes = Yii::$app->request->post(’ContactForm’);
if ($model->validate()) {
// all inputs are valid
} else {
// validation failed: $errors is an array containing error messages
$errors = $model->errors;
}
Behind the scene, the validate() method does the following steps to perform
validation:
1. Determine which attributes should be validated by getting the attrib-
ute list from yiibaseModel::scenarios() using the current yii
baseModel::scenario. These attributes are called active attributes.
2. Determine which validation rules should be used by getting the rule list
from yiibaseModel::rules() using the current yiibaseModel::
scenario. These rules are called active rules.
3. Use each active rule to validate each active attribute associated with
that rule. If the rule fails, keep an error message for the attribute in
the model.
7.2.1 Declaring Rules
To make validate() really work, you should declare validation rules for the
attributes you plan to validate. This should be done by overriding the yii
baseModel::rules() method. The following example shows how the val-
idation rules for the ContactForm model are declared:
public function rules()
{
return [
// the name, email, subject and body attributes are required
[[’name’, ’email’, ’subject’, ’body’], ’required’],
264 CHAPTER 7. GETTING DATA FROM USERS
// the email attribute should be a valid email address
[’email’, ’email’],
];
}
The yiibaseModel::rules() method should return an array of rules, each
of which is an array of the following format:
[
// required, specifies which attributes should be validated by this rule
.
// For a single attribute, you can use the attribute name directly
// without having it in an array instead of an array
[’attribute1’, ’attribute2’, ...],
// required, specifies the type of this rule.
// It can be a class name, validator alias, or a validation method name
’validator’,
// optional, specifies in which scenario(s) this rule should be applied
// if not given, it means the rule applies to all scenarios
// You may also configure the "except" option if you want to apply the
rule
// to all scenarios except the listed ones
’on’ => [’scenario1’, ’scenario2’, ...],
// optional, specifies additional configurations for the validator
object
’property1’ => ’value1’, ’property2’ => ’value2’, ...
]
For each rule you must specify at least which attributes the rule applies to
and what is the type of the rule. You can specify the rule type in one of the
following forms:
• the alias of a core validator, such as required, in, date, etc. Please refer
to the Core Validators for the complete list of core validators.
• the name of a validation method in the model class, or an anonymous
function. Please refer to the Inline Validators subsection for more
details.
• the name of a validator class. Please refer to the Standalone Validators
subsection for more details.
A rule can be used to validate one or multiple attributes, and an attribute
may be validated by one or multiple rules. A rule may be applied in certain
scenarios only by specifying the on option. If you do not specify an on option,
it means the rule will be applied to all scenarios.
When the validate() method is called, it does the following steps to
perform validation:
7.2. VALIDATING INPUT 265
1. Determine which attributes should be validated by checking the cur-
rent yiibaseModel::scenario against the scenarios declared in yii
baseModel::scenarios(). These attributes are the active attrib-
utes.
2. Determine which rules should be applied by checking the current yii
baseModel::scenario against the rules declared in yiibaseModel
::rules(). These rules are the active rules.
3. Use each active rule to validate each active attribute which is associated
with the rule. The validation rules are evaluated in the order they are
listed.
According to the above validation steps, an attribute will be validated if and
only if it is an active attribute declared in scenarios() and is associated with
one or multiple active rules declared in rules().
Customizing Error Messages
Most validators have default error messages that will be added to the model
being validated when its attributes fail the validation. For example, the yii
validatorsRequiredValidator validator will add a message “Username
cannot be blank.” to a model when the username attribute fails the rule using
this validator.
You can customize the error message of a rule by specifying the message
property when declaring the rule, like the following,
public function rules()
{
return [
[’username’, ’required’, ’message’ => ’Please choose a username.’],
];
}
Some validators may support additional error messages to more precisely de-
scribe different causes of validation failures. For example, the yiivalidators
NumberValidator validator supports yiivalidatorsNumberValidator
::tooBig and yiivalidatorsNumberValidator::tooSmall to describe
the validation failure when the value being validated is too big and too
small, respectively. You may configure these error messages like configuring
other properties of validators in a validation rule.
Validation Events
When yiibaseModel::validate() is called, it will call two methods that
you may override to customize the validation process:
266 CHAPTER 7. GETTING DATA FROM USERS
• yiibaseModel::beforeValidate(): the default implementation will
trigger a yiibaseModel::EVENT_BEFORE_VALIDATE event. You may
either override this method or respond to this event to do some pre-
processing work (e.g. normalizing data inputs) before the validation
occurs. The method should return a boolean value indicating whether
the validation should proceed or not.
• yiibaseModel::afterValidate(): the default implementation will
trigger a yiibaseModel::EVENT_AFTER_VALIDATE event. You may
either override this method or respond to this event to do some post-
processing work after the validation is completed.
Conditional Validation
To validate attributes only when certain conditions apply, e.g. the validation
of one attribute depends on the value of another attribute you can use the
yiivalidatorsValidator::when property to define such conditions. For
example,
[
[’state’, ’required’, ’when’ => function($model) {
return $model->country == ’USA’;
}],
]
The yiivalidatorsValidator::when property takes a PHP callable with
the following signature:
/**
* @param Model $model the model being validated
* @param string $attribute the attribute being validated
* @return boolean whether the rule should be applied
*/
function ($model, $attribute)
If you also need to support client-side conditional validation, you should con-
figure the yiivalidatorsValidator::whenClient property which takes
a string representing a JavaScript function whose return value determines
whether to apply the rule or not. For example,
[
[’state’, ’required’, ’when’ => function ($model) {
return $model->country == ’USA’;
}, ’whenClient’ => "function (attribute, value) {
return $(’#country’).val() == ’USA’;
}"],
]
7.2. VALIDATING INPUT 267
Data Filtering
User inputs often need to be filtered or preprocessed. For example, you may
want to trim the spaces around the username input. You may use validation
rules to achieve this goal.
The following examples shows how to trim the spaces in the inputs and
turn empty inputs into nulls by using the trim and default core validators:
[
[[’username’, ’email’], ’trim’],
[[’username’, ’email’], ’default’],
]
You may also use the more general filter validator to perform more complex
data filtering.
As you can see, these validation rules do not really validate the inputs.
Instead, they will process the values and save them back to the attributes
being validated.
Handling Empty Inputs
When input data are submitted from HTML forms, you often need to assign
some default values to the inputs if they are empty. You can do so by using
the default validator. For example,
[
// set "username" and "email" as null if they are empty
[[’username’, ’email’], ’default’],
// set "level" to be 1 if it is empty
[’level’, ’default’, ’value’ => 1],
]
By default, an input is considered empty if its value is an empty string, an
empty array or a null. You may customize the default empty detection logic
by configuring the the yiivalidatorsValidator::isEmpty property with
a PHP callable. For example,
[
[’agree’, ’required’, ’isEmpty’ => function ($value) {
return empty($value);
}],
]
Note: Most validators do not handle empty inputs if their yii
baseValidator::skipOnEmpty property takes the default value
true. They will simply be skipped during validation if their asso-
ciated attributes receive empty inputs. Among the core validat-
ors, only the captcha, default, filter, required, and trim validators
will handle empty inputs.
268 CHAPTER 7. GETTING DATA FROM USERS
7.2.2 Ad Hoc Validation
Sometimes you need to do ad hoc validation for values that are not bound
to any model.
If you only need to perform one type of validation (e.g. validating email
addresses), you may call the yiivalidatorsValidator::validate() method
of the desired validator, like the following:
$email = ’test@example.com’;
$validator = new yiivalidatorsEmailValidator();
if ($validator->validate($email, $error)) {
echo ’Email is valid.’;
} else {
echo $error;
}
Note: Not all validators support such kind of validation. An
example is the unique core validator which is designed to work
with a model only.
If you need to perform multiple validations against several values, you can
use yiibaseDynamicModel which supports declaring both attributes and
rules on the fly. Its usage is like the following:
public function actionSearch($name, $email)
{
$model = DynamicModel::validateData(compact(’name’, ’email’), [
[[’name’, ’email’], ’string’, ’max’ => 128],
[’email’, ’email’],
]);
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
}
The yiibaseDynamicModel::validateData() method creates an instance
of DynamicModel, defines the attributes using the given data (name and email in
this example), and then calls yiibaseModel::validate() with the given
rules.
Alternatively, you may use the following more “classic” syntax to perform
ad hoc data validation:
public function actionSearch($name, $email)
{
$model = new DynamicModel(compact(’name’, ’email’));
$model->addRule([’name’, ’email’], ’string’, [’max’ => 128])
->addRule(’email’, ’email’)
->validate();
7.2. VALIDATING INPUT 269
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
}
After validation, you can check if the validation succeeds or not by calling
the yiibaseDynamicModel::hasErrors() method, and then get the val-
idation errors from the yiibaseDynamicModel::errors property, like you
do with a normal model. You may also access the dynamic attributes defined
through the model instance, e.g., $model->name and $model->email.
7.2.3 Creating Validators
Besides using the core validators included in the Yii releases, you may also
create your own validators. You may create inline validators or standalone
validators.
Inline Validators
An inline validator is one defined in terms of a model method or an anonym-
ous function. The signature of the method/function is:
/**
* @param string $attribute the attribute currently being validated
* @param array $params the additional name-value pairs given in the rule
*/
function ($attribute, $params)
If an attribute fails the validation, the method/function should call yiibase
Model::addError() to save the error message in the model so that it can
be retrieved back later to present to end users.
Below are some examples:
use yiibaseModel;
class MyForm extends Model
{
public $country;
public $token;
public function rules()
{
return [
// an inline validator defined as the model method
validateCountry()
[’country’, ’validateCountry’],
// an inline validator defined as an anonymous function
[’token’, function ($attribute, $params) {
270 CHAPTER 7. GETTING DATA FROM USERS
if (!ctype_alnum($this->$attribute)) {
$this->addError($attribute, ’The token must contain
letters or digits.’);
}
}],
];
}
public function validateCountry($attribute, $params)
{
if (!in_array($this->$attribute, [’USA’, ’Web’])) {
$this->addError($attribute, ’The country must be either "USA" or
"Web".’);
}
}
}
Note: By default, inline validators will not be applied if their as-
sociated attributes receive empty inputs or if they have already
failed some validation rules. If you want to make sure a rule is al-
ways applied, you may configure the yiivalidatorsValidator
::skipOnEmpty and/or yiivalidatorsValidator::skipOnError
properties to be false in the rule declarations. For example: ‘php
[
[’country’, ’validateCountry’, ’skipOnEmpty’ => false, ’
skipOnError’ => false],
] ‘
Standalone Validators
A standalone validator is a class extending yiivalidatorsValidator or
its child class. You may implement its validation logic by overriding the
yiivalidatorsValidator::validateAttribute() method. If an attrib-
ute fails the validation, call yiibaseModel::addError() to save the error
message in the model, like you do with inline validators. For example,
namespace appcomponents;
use yiivalidatorsValidator;
class CountryValidator extends Validator
{
public function validateAttribute($model, $attribute)
{
if (!in_array($model->$attribute, [’USA’, ’Web’])) {
$this->addError($attribute, ’The country must be either "USA" or
"Web".’);
}
}
}
7.2. VALIDATING INPUT 271
If you want your validator to support validating a value without a model,
you should also override yiivalidatorsValidator::validate(). You
may also override yiivalidatorsValidator::validateValue() instead
of validateAttribute() and validate() because by default the latter two meth-
ods are implemented by calling validateValue().
7.2.4 Client-Side Validation
Client-side validation based on JavaScript is desirable when end users provide
inputs via HTML forms, because it allows users to find out input errors faster
and thus provides better user experience. You may use or implement a valid-
ator that supports client-side validation in addition to server-side validation.
Info: While client-side validation is desirable, it is not a must. Its
main purpose is to provide users better experience. Like input
data coming from end users, you should never trust client-side
validation. For this reason, you should always perform server-
side validation by calling yiibaseModel::validate(), like de-
scribed in the previous subsections.
Using Client-Side Validation
Many core validators support client-side validation out-of-box. All you need
to do is just to use yiiwidgetsActiveForm to build your HTML forms.
For example, LoginForm below declares two rules: one uses the required core
validator which is supported on both client and server sides; the other uses
the validatePassword inline validator which is only supported on the server
side.
namespace appmodels;
use yiibaseModel;
use appmodelsUser;
class LoginForm extends Model
{
public $username;
public $password;
public function rules()
{
return [
// username and password are both required
[[’username’, ’password’], ’required’],
// password is validated by validatePassword()
[’password’, ’validatePassword’],
];
}
272 CHAPTER 7. GETTING DATA FROM USERS
public function validatePassword()
{
$user = User::findByUsername($this->username);
if (!$user || !$user->validatePassword($this->password)) {
$this->addError(’password’, ’Incorrect username or password.’);
}
}
}
The HTML form built by the following code contains two input fields username
and password. If you submit the form without entering anything, you will
find the error messages requiring you to enter something appear right away
without any communication with the server.
<?php $form = yiiwidgetsActiveForm::begin(); ?>
<?= $form->field($model, ’username’) ?>
<?= $form->field($model, ’password’)->passwordInput() ?>
<?= Html::submitButton(’Login’) ?>
<?php yiiwidgetsActiveForm::end(); ?>
Behind the scene, yiiwidgetsActiveForm will read the validation rules
declared in the model and generate appropriate JavaScript code for validators
that support client-side validation. When a user changes the value of an
input field or submit the form, the client-side validation JavaScript will be
triggered.
If you want to turn off client-side validation completely, you may config-
ure the yiiwidgetsActiveForm::enableClientValidation property to
be false. You may also turn off client-side validation of individual input fields
by configuring their yiiwidgetsActiveField::enableClientValidation
property to be false.
Implementing Client-Side Validation
To create a validator that supports client-side validation, you should imple-
ment the yiivalidatorsValidator::clientValidateAttribute() method
which returns a piece of JavaScript code that performs the validation on the
client side. Within the JavaScript code, you may use the following predefined
variables:
• attribute: the name of the attribute being validated.
• value: the value being validated.
• messages: an array used to hold the validation error messages for the
attribute.
• deferred: an array which deferred objects can be pushed into (explained
in the next subsection).
7.2. VALIDATING INPUT 273
In the following example, we create a StatusValidator which validates if an
input is a valid status input against the existing status data. The validator
supports both server side and client side validation.
namespace appcomponents;
use yiivalidatorsValidator;
use appmodelsStatus;
class StatusValidator extends Validator
{
public function init()
{
parent::init();
$this->message = ’Invalid status input.’;
}
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!Status::find()->where([’id’ => $value])->exists()) {
$model->addError($attribute, $this->message);
}
}
public function clientValidateAttribute($model, $attribute, $view)
{
$statuses = json_encode(Status::find()->select(’id’)->asArray()->
column());
$message = json_encode($this->message);
return <<<JS
if (!$.inArray(value, $statuses)) {
messages.push($message);
}
JS;
}
}
Tip: The above code is given mainly to demonstrate how to sup-
port client-side validation. In practice, you may use the in core
validator to achieve the same goal. You may write the validation
rule like the following: ‘php [
[’status’, ’in’, ’range’ => Status::find()->select(’id’)->
asArray()->column()],
] ‘
274 CHAPTER 7. GETTING DATA FROM USERS
Deferred Validation
If you need to perform asynchronous client-side validation, you can create
Deferred objects1. For example, to perform a custom AJAX validation, you
can use the following code:
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.push($.get("/check", {value: value}).done(function(data) {
if (’’ !== data) {
messages.push(data);
}
}));
JS;
}
In the above, the deferred variable is provided by Yii, which is an array
of Deferred objects. The $.get() jQuery method creates a Deferred object
which is pushed to the deferred array.
You can also explicitly create a Deferred object and call its resolve()
method when the asynchronous callback is hit. The following example shows
how to validate the dimensions of an uploaded image file on the client side.
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
var def = $.Deferred();
var img = new Image();
img.onload = function() {
if (this.width > 150) {
messages.push(’Image too wide!!’);
}
def.resolve();
}
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
}
reader.readAsDataURL(file);
deferred.push(def);
JS;
}
Note: The resolve() method must be called after the attribute
has been validated. Otherwise the main form validation will not
complete.
For simplicity, the deferred array is equipped with a shortcut method add()
which automatically creates a Deferred object and add it to the deferred
1
http://api.jquery.com/category/deferred-object/
7.2. VALIDATING INPUT 275
array. Using this method, you can simplify the above example as follows,
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.add(function(def) {
var img = new Image();
img.onload = function() {
if (this.width > 150) {
messages.push(’Image too wide!!’);
}
def.resolve();
}
var reader = new FileReader();
reader.onloadend = function() {
img.src = reader.result;
}
reader.readAsDataURL(file);
});
JS;
}
AJAX Validation
Some validations can only be done on the server side, because only the
server has the necessary information. For example, to validate if a username
is unique or not, it is necessary to check the user table on the server side.
You can use AJAX-based validation in this case. It will trigger an AJAX
request in the background to validate the input while keeping the same user
experience as the regular client-side validation.
To enable AJAX validation for the whole form, you have to set the
yiiwidgetsActiveForm::enableAjaxValidation property to be true and
specify id to be unique form identifier:
<?php $form = yiiwidgetsActiveForm::begin([
’id’ => ’contact-form’,
’enableAjaxValidation’ => true,
]); ?>
You may also turn AJAX validation on or off for individual input fields by
configuring their yiiwidgetsActiveField::enableAjaxValidation prop-
erty.
You also need to prepare the server so that it can handle the AJAX
validation requests. This can be achieved by a code snippet like the following
in controller actions:
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post()))
{
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
276 CHAPTER 7. GETTING DATA FROM USERS
The above code will check whether the current request is an AJAX. If yes,
it will respond to this request by running the validation and returning the
errors in JSON format.
Info: You can also use Deferred Validation to perform AJAX
validation. However, the AJAX validation feature described here
is more systematic and requires less coding effort.
7.3 Uploading Files
Note: This section is under development.
Uploading files in Yii is done via form model, its validation rules and some
controller code. Let’s review what’s needed to handle uploads properly.
7.3.1 Form model
First of all, you need to create a model that will handle file upload. Create
models/UploadForm.php with the following content:
namespace appmodels;
use yiibaseModel;
use yiiwebUploadedFile;
/**
* UploadForm is the model behind the upload form.
*/
class UploadForm extends Model
{
/**
* @var UploadedFile|Null file attribute
*/
public $file;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[[’file’], ’file’],
];
}
}
In the code above, we created a model UploadForm with an attribute $file
that will become <input type="file"> in the HTML form. The attribute has
the validation rule named file that uses yiivalidatorsFileValidator.
7.3. UPLOADING FILES 277
7.3.2 Form view
Next create a view that will render the form.
<?php
use yiiwidgetsActiveForm;
$form = ActiveForm::begin([’options’ => [’enctype’ => ’multipart/form-data’
]]); ?>
<?= $form->field($model, ’file’)->fileInput() ?>
<button>Submit</button>
<?php ActiveForm::end(); ?>
The ’enctype’ => ’multipart/form-data’ is important since it allows file up-
loads. fileInput() represents a form input field.
7.3.3 Controller
Now create the controller that connects form and model together:
namespace appcontrollers;
use Yii;
use yiiwebController;
use appmodelsUploadForm;
use yiiwebUploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->file = UploadedFile::getInstance($model, ’file’);
if ($model->validate()) {
$model->file->saveAs(’uploads/’ . $model->file->baseName . ’
.’ . $model->file->extension);
}
}
return $this->render(’upload’, [’model’ => $model]);
}
}
Instead of model->load(...) we are using UploadedFile::getInstance(...). yii
webUploadedFile does not run the model validation. It only provides
information about the uploaded file. Therefore, you need to run valid-
ation manually via $model->validate(). This triggers the yiivalidators
FileValidator that expects a file:
278 CHAPTER 7. GETTING DATA FROM USERS
$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE //in
code framework
If validation is successful, then we’re saving the file:
$model->file->saveAs(’uploads/’ . $model->file->baseName . ’.’ . $model->
file->extension);
If you’re using “basic” application template then folder uploads should be
created under web.
That’s it. Load the page and try uploading. Uplaods should end up in
basic/web/uploads.
7.3.4 Additional information
Required rule
If you need to make file upload mandatory use skipOnEmpty like the following:
public function rules()
{
return [
[[’file’], ’file’, ’skipOnEmpty’ => false],
];
}
MIME type
It is wise to validate type of the file uploaded. FileValidator has property
$extensions for the purpose:
public function rules()
{
return [
[[’file’], ’file’, ’extensions’ => ’gif, jpg’,],
];
}
The thing is that it validates only file extension and not the file content. In
order to validate content as well use mimeTypes property of FileValidator:
public function rules()
{
return [
[[’file’], ’file’, ’extensions’ => ’jpg, png’, ’mimeTypes’ => ’image
/jpeg, image/png’,],
];
}
List of common media types2
2
http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_
types
7.3. UPLOADING FILES 279
Validating uploaded image
If you upload an image, yiivalidatorsImageValidator may come in
handy. It verifies if an attribute received a valid image that can be then
either saved or processed using Imagine Extension3.
Uploading multiple files
If you need download multiple files at once some adjustments are required.
View:
<?php
use yiiwidgetsActiveForm;
$form = ActiveForm::begin([’options’ => [’enctype’ => ’multipart/form-data’
]]);
if ($model->hasErrors()) { //it is necessary to see all the errors for all
the files.
echo ’<pre>’;
print_r($model->getErrors());
echo ’</pre>’;
}
?>
<?= $form->field($model, ’file[]’)->fileInput([’multiple’ => ’’]) ?>
<button>Submit</button>
<?php ActiveForm::end(); ?>
The difference is the following line:
<?= $form->field($model, ’file[]’)->fileInput([’multiple’ => ’’]) ?>
Controller:
namespace appcontrollers;
use Yii;
use yiiwebController;
use appmodelsUploadForm;
use yiiwebUploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$files = UploadedFile::getInstances($model, ’file’);
3
https://github.com/yiisoft/yii2/tree/master/extensions/imagine
280 CHAPTER 7. GETTING DATA FROM USERS
foreach ($files as $file) {
$_model = new UploadForm();
$_model->file = $file;
if ($_model->validate()) {
$_model->file->saveAs(’uploads/’ . $_model->file->
baseName . ’.’ . $_model->file->extension);
} else {
foreach ($_model->getErrors(’file’) as $error) {
$model->addError(’file’, $error);
}
}
}
if ($model->hasErrors(’file’)){
$model->addError(
’file’,
count($model->getErrors(’file’)) . ’ of ’ . count($files
) . ’ files not uploaded’
);
}
}
return $this->render(’upload’, [’model’ => $model]);
}
}
The difference is UploadedFile::getInstances($model, ’file’); instead of UploadedFile
::getInstance($model, ’file’);. Former returns instances for all uploaded
files while the latter gives you only a single instance.
7.3. UPLOADING FILES 281
Error: not existing file: input-multiple-models.md
282 CHAPTER 7. GETTING DATA FROM USERS
Chapter 8
Displaying Data
8.1 Data Formatter
For formatting of outputs Yii provides a formatter class to make data more
readable for users. yiii18nFormatter is a helper class that is registered
as an application component named formatter by default.
It provides a set of methods for data formatting purpose such as date/-
time values, numbers and other commonly used formats in a localized way.
The formatter can be used in two different ways.
1. Using the formatting methods (all formatter methods prefixed with as)
directly:
echo Yii::$app->formatter->asDate(’2014-01-01’, ’long’); // output:
January 1, 2014
echo Yii::$app->formatter->asPercent(0.125, 2); // output: 12.50%
echo Yii::$app->formatter->asEmail(’cebe@example.com’); // output: <a
href="mailto:cebe@example.com">cebe@example.com</a>
echo Yii::$app->formatter->asBoolean(true); // output: Yes
// it also handles display of null values:
echo Yii::$app->formatter->asDate(null); // output: (Not set)
2. Using the yiii18nFormatter::format() method and the format
name. This method is also used by widgets like yiigridGridView
and yiiwidgetsDetailView where you can specify the data format
of a column in the widget configuration.
echo Yii::$app->formatter->format(’2014-01-01’, ’date’); // output:
January 1, 2014
// you can also use an array to specify parameters for the format
method:
// ‘2‘ is the value for the $decimals parameter of the asPercent()-
method.
echo Yii::$app->formatter->format(0.125, [’percent’, 2]); // output:
12.50%
283
284 CHAPTER 8. DISPLAYING DATA
All output of the formatter is localized when the PHP intl extension1 is
installed. You can configure the yiii18nFormatter::locale property of
the formatter for this. If not configured, the application yiibaseApplication
::language is used as the locale. See the Section on internationaization for
more details. The Formatter will then choose the correct format for dates
and numbers according to the locale including names of month and week
days translated to the current language. Date formats are also affected
by the yiii18nFormatter::timeZone which will also be taken yiibase
Application::timeZone if not configured explicitly.
For example the date format call will output different results for different
locales:
Yii::$app->formatter->locale = ’en-US’;
echo Yii::$app->formatter->asDate(’2014-01-01’); // output: January 1, 2014
Yii::$app->formatter->locale = ’de-DE’;
echo Yii::$app->formatter->asDate(’2014-01-01’); // output: 1. Januar 2014
Yii::$app->formatter->locale = ’ru-RU’;
echo Yii::$app->formatter->asDate(’2014-01-01’); // output: 1 января 2014 г.
Note that formatting may differ between different versions of
the ICU library compiled with PHP and also based on the fact
whether the PHP intl extension2 is installed or not. So to ensure
your website works with the same output in all environments it
is recommended to install the PHP intl extension in all environ-
ments and verify that the version of the ICU library is the same.
See also: Setting up your PHP environment for internationaliza-
tion.
8.1.1 Configuring the format
The default format of the Formatter class can be adjusted using the proper-
ties of the formatter class. You can adjust these values application wide by
configuring the formatter component in your application config. An example
configuration is shown in the following. For more details about the available
properties check out the yiii18nFormatter.
’components’ => [
’formatter’ => [
’dateFormat’ => ’dd.MM.yyyy’,
’decimalSeparator’ => ’,’,
’thousandSeparator’ => ’ ’,
’currencyCode’ => ’EUR’,
];
1
http://php.net/manual/en/book.intl.php
2
http://php.net/manual/en/book.intl.php
8.1. DATA FORMATTER 285
8.1.2 Formatting Dates
Note: This section is under development.
TDB
See http://site.icu-project.org/ for the format.
• yiii18nFormatter::asDate() - the value is formatted as date.
• yiii18nFormatter::asTime() - the value is formatted as time.
• yiii18nFormatter::asDatetime() - the value is formatted as dat-
etime.
• yiii18nFormatter::asTimestamp() - the value is formatted as a
unix timestamp.
• yiii18nFormatter::asRelativeTime() - the value is formatted as
the time interval between a date and now in human readable form.
The input value for date and time formatting is assumed to be in UTC unless
a timezone is explicitly given.
8.1.3 Formatting Numbers
Note: This section is under development.
TDB
See http://site.icu-project.org/ for the format.
• yiii18nFormatter::asInteger() - the value is formatted as an
integer.
• yiii18nFormatter::asDecimal() - the value is formatted as a
number with decimal and thousand separators.
• yiii18nFormatter::asPercent() - the value is formatted as a per-
cent number.
• yiii18nFormatter::asScientific() - the value is formatted as a
number in scientific format.
• yiii18nFormatter::asCurrency() - the value is formatted as a
currency value.
• yiii18nFormatter::asSize() - the value that is a number of bytes
is formatted as a human readable size.
• yiii18nFormatter::asShortSize() - the value that is a number
of bytes is formatted as a human readable size.
286 CHAPTER 8. DISPLAYING DATA
8.1.4 Other formatters
Note: This section is under development.
TDB
Here’s the bundled formatters list:
• yiii18nFormatter::asRaw() - the value is outputted as is.
• yiii18nFormatter::asText() - the value is HTML-encoded. This
format is used by default.
• yiii18nFormatter::asNtext() - the value is formatted as an HTML-
encoded plain text with newlines converted into line breaks.
• yiii18nFormatter::asParagraphs() - the value is formatted as
HTML-encoded text paragraphs wrapped into <p> tags.
• yiii18nFormatter::asHtml() - the value is purified using HtmlPurifier
to avoid XSS attacks. You can pass additional options such as [’html’
, [’Attr.AllowedFrameTargets’ => [’_blank’]]].
• yiii18nFormatter::asEmail() - the value is formatted as a mailto
link.
• yiii18nFormatter::asImage() - the value is formatted as an im-
age tag.
• yiii18nFormatter::asUrl() - the value is formatted as a hyper-
link.
• yiii18nFormatter::asBoolean() - the value is formatted as a
boolean. You can set what’s rendered for true and false values by
calling Yii::$app->formatter->booleanFormat = [’No’, ’Yes’]; before out-
putting GridView.
8.1. DATA FORMATTER 287
Error: not existing file: output-pagination.md
288 CHAPTER 8. DISPLAYING DATA
Error: not existing file: output-sorting.md
8.2. DATA PROVIDERS 289
8.2 Data providers
Note: This section is under development.
Data provider abstracts data set via yiidataDataProviderInterface and
handles pagination and sorting. It can be used by grids, lists and other data
widgets.
In Yii there are three built-in data providers: yiidataActiveDataProvider,
yiidataArrayDataProvider and yiidataSqlDataProvider.
8.2.1 Active data provider
ActiveDataProvider provides data by performing DB queries using yiidb
Query and yiidbActiveQuery.
The following is an example of using it to provide ActiveRecord instances:
$provider = new ActiveDataProvider([
’query’ => Post::find(),
’pagination’ => [
’pageSize’ => 20,
],
]);
// get the posts in the current page
$posts = $provider->getModels();
And the following example shows how to use ActiveDataProvider without
ActiveRecord:
$query = new Query();
$provider = new ActiveDataProvider([
’query’ => $query->from(’post’),
’sort’ => [
// Set the default sort by name ASC and created_at DESC.
’defaultOrder’ => [
’name’ => SORT_ASC,
’created_at’ => SORT_DESC
]
],
’pagination’ => [
’pageSize’ => 20,
],
]);
// get the posts in the current page
$posts = $provider->getModels();
8.2.2 Array data provider
ArrayDataProvider implements a data provider based on a data array.
290 CHAPTER 8. DISPLAYING DATA
The yiidataArrayDataProvider::$allModels property contains all
data models that may be sorted and/or paginated. ArrayDataProvider will
provide the data after sorting and/or pagination. You may configure the
yiidataArrayDataProvider::$sort and yiidataArrayDataProvider
::$pagination properties to customize the sorting and pagination behavi-
ors.
Elements in the yiidataArrayDataProvider::$allModels array may
be either objects (e.g. model objects) or associative arrays (e.g. query
results of DAO). Make sure to set the yiidataArrayDataProvider::$key
property to the name of the field that uniquely identifies a data record or
false if you do not have such a field.
Compared to ActiveDataProvider, ArrayDataProvider could be less efficient
because it needs to have yiidataArrayDataProvider::$allModels ready.
ArrayDataProvider may be used in the following way:
$query = new Query();
$provider = new ArrayDataProvider([
’allModels’ => $query->from(’post’)->all(),
’sort’ => [
’attributes’ => [’id’, ’username’, ’email’],
],
’pagination’ => [
’pageSize’ => 10,
],
]);
// get the posts in the current page
$posts = $provider->getModels();
Note: if you want to use the sorting feature, you must configure
the sort property so that the provider knows which columns can
be sorted.
8.2.3 SQL data provider
SqlDataProvider implements a data provider based on a plain SQL state-
ment. It provides data in terms of arrays, each representing a row of query
result.
Like other data providers, SqlDataProvider also supports sorting and
pagination. It does so by modifying the given yiidataSqlDataProvider::
$sql statement with “ORDER BY” and “LIMIT” clauses. You may configure
the yiidataSqlDataProvider::$sort and yiidataSqlDataProvider
::$pagination properties to customize sorting and pagination behaviors.
SqlDataProvider may be used in the following way:
$count = Yii::$app->db->createCommand(’
SELECT COUNT(*) FROM user WHERE status=:status
’, [’:status’ => 1])->queryScalar();
8.3. DATA WIDGETS 291
$dataProvider = new SqlDataProvider([
’sql’ => ’SELECT * FROM user WHERE status=:status’,
’params’ => [’:status’ => 1],
’totalCount’ => $count,
’sort’ => [
’attributes’ => [
’age’,
’name’ => [
’asc’ => [’first_name’ => SORT_ASC, ’last_name’ => SORT_ASC
],
’desc’ => [’first_name’ => SORT_DESC, ’last_name’ =>
SORT_DESC],
’default’ => SORT_DESC,
’label’ => ’Name’,
],
],
],
’pagination’ => [
’pageSize’ => 20,
],
]);
// get the user records in the current page
$models = $dataProvider->getModels();
Note: if you want to use the pagination feature, you must con-
figure the yiidataSqlDataProvider::$totalCount property
to be the total number of rows (without pagination). And if you
want to use the sorting feature, you must configure the yiidata
SqlDataProvider::$sort property so that the provider knows
which columns can be sorted.
8.2.4 Implementing your own custom data provider
TBD
8.3 Data widgets
Note: This section is under development.
8.3.1 ListView
8.3.2 DetailView
DetailView displays the detail of a single data yiiwidgetsDetailView::$model.
It is best used for displaying a model in a regular format (e.g. each model attribute is displayed as a row
The model can be either an instance of yiibaseModel or an associative array.
292 CHAPTER 8. DISPLAYING DATA
DetailView uses the yiiwidgetsDetailView::$attributes property
to determines which model attributes should be displayed and how they
should be formatted.
A typical usage of DetailView is as follows:
echo DetailView::widget([
’model’ => $model,
’attributes’ => [
’title’, // title attribute (in plain text)
’description:html’, // description attribute in HTML
[ // the owner name of the model
’label’ => ’Owner’,
’value’ => $model->owner->name,
],
],
]);
8.3.3 GridView
Data grid or GridView is one of the most powerful Yii widgets. It is extremely
useful if you need to quickly build admin section of the system. It takes data
from data provider and renders each row using a set of columns presenting
data in a form of a table.
Each row of the table represents the data of a single data item, and
a column usually represents an attribute of the item (some columns may
correspond to complex expression of attributes or static text).
Grid view supports both sorting and pagination of the data items. The
sorting and pagination can be done in AJAX mode or normal page request.
A benefit of using GridView is that when the user disables JavaScript, the
sorting and pagination automatically degrade to normal page requests and
are still functioning as expected.
The minimal code needed to use GridView is as follows:
use yiigridGridView;
use yiidataActiveDataProvider;
$dataProvider = new ActiveDataProvider([
’query’ => Post::find(),
’pagination’ => [
’pageSize’ => 20,
],
]);
echo GridView::widget([
’dataProvider’ => $dataProvider,
]);
The above code first creates a data provider and then uses GridView to
display every attribute in every row taken from data provider. The displayed
table is equipped with sorting and pagination functionality.
8.3. DATA WIDGETS 293
Grid columns
Yii grid consists of a number of columns. Depending on column type and
settings these are able to present data differently.
These are defined in the columns part of GridView config like the follow-
ing:
echo GridView::widget([
’dataProvider’ => $dataProvider,
’columns’ => [
[’class’ => ’yiigridSerialColumn’],
// A simple column defined by the data contained in $dataProvider.
// Data from model’s column1 will be used.
’id’,
’username’,
// More complex one.
[
’class’ => ’yiigridDataColumn’, // can be omitted, default
’value’ => function ($data) {
return $data->name; //$data[’name’] for array data, e.g.
using SqlDataProvider.
},
],
],
]);
Note that if columns part of config isn’t specified, Yii tries to show all possible
data provider model columns.
Column classes
Grid columns could be customized by using different column classes:
echo GridView::widget([
’dataProvider’ => $dataProvider,
’columns’ => [
[
’class’ => ’yiigridSerialColumn’, // <-- here
// you may configure additional properties here
],
Additionally to column classes provided by Yii that we’ll review below you
can create your own column classes.
Each column class extends from yiigridColumn so there some com-
mon options you can set while configuring grid columns.
• header allows to set content for header row.
• footer allows to set content for footer row.
• visible is the column should be visible.
294 CHAPTER 8. DISPLAYING DATA
• content allows you to pass a valid PHP callback that will return data
for a row. The format is the following:
function ($model, $key, $index, $column) {
return ’a string’;
}
You may specify various container HTML options passing arrays to:
• headerOptions
• contentOptions
• footerOptions
• filterOptions
Data column Data column is for displaying and sorting data. It is default
column type so specifying class could be omitted when using it.
The main setting of the data column is its format. It could be spe-
cified via format attribute. Its values are corresponding to methods in format
application component that is yiii18nFormatter by default:
<?= GridView::widget([
’columns’ => [
[
’attribute’ => ’name’,
’format’ => ’text’
],
[
’attribute’ => ’birthday’,
’format’ => [’date’, ’Y-m-d’]
],
],
]); ?>
In the above text corresponds to yiii18nFormatter::asText(). The
value of the column is passed as the first argument. In the second column
definition date corresponds to yiii18nFormatter::asDate(). The value
of the column is, again, passed as the first argument while ‘Y-m-d’ is used
as the second argument value.
For a list of available formatters see the section about Data Formatting.
Action column Action column displays action buttons such as update or
delete for each row.
echo GridView::widget([
’dataProvider’ => $dataProvider,
’columns’ => [
[
8.3. DATA WIDGETS 295
’class’ => ’yiigridActionColumn’,
// you may configure additional properties here
],
Available properties you can configure are:
• controller is the ID of the controller that should handle the actions. If
not set, it will use the currently active controller.
• template the template used for composing each cell in the action column.
Tokens enclosed within curly brackets are treated as controller action
IDs (also called button names in the context of action column). They
will be replaced by the corresponding button rendering callbacks spe-
cified in yiigridActionColumn::$buttons. For example, the token
{view} will be replaced by the result of the callback buttons[’view’]. If
a callback cannot be found, the token will be replaced with an empty
string. Default is {view} {update} {delete}.
• buttons is an array of button rendering callbacks. The array keys are
the button names (without curly brackets), and the values are the
corresponding button rendering callbacks. The callbacks should use
the following signature:
function ($url, $model) {
// return the button HTML code
}
In the code above $url is the URL that the column creates for the button,
and $model is the model object being rendered for the current row.
• urlCreator is a callback that creates a button URL using the specified
model information. The signature of the callback should be the same
as that of yiigridActionColumn::createUrl(). If this property is
not set, button URLs will be created using yiigridActionColumn
::createUrl().
Checkbox column CheckboxColumn displays a column of checkboxes.
To add a CheckboxColumn to the yiigridGridView, add it to the yii
gridGridView::$columns configuration as follows:
echo GridView::widget([
’dataProvider’ => $dataProvider,
’columns’ => [
// ...
[
’class’ => ’yiigridCheckboxColumn’,
// you may configure additional properties here
],
],
296 CHAPTER 8. DISPLAYING DATA
Users may click on the checkboxes to select rows of the grid. The selected
rows may be obtained by calling the following JavaScript code:
var keys = $(’#grid’).yiiGridView(’getSelectedRows’);
// keys is an array consisting of the keys associated with the selected rows
Serial column Serial column renders row numbers starting with 1 and
going forward.
Usage is as simple as the following:
echo GridView::widget([
’dataProvider’ => $dataProvider,
’columns’ => [
[’class’ => ’yiigridSerialColumn’], // <-- here
// ...
Sorting data
• https://github.com/yiisoft/yii2/issues/1576
Filtering data
For filtering data the GridView needs a model that takes the input from the
filtering form and adjusts the query of the dataProvider to respect the search
criteria. A common practice when using active records is to create a search
Model class that provides needed functionality (it can be generated for you
by Gii). This class defines the validation rules for the search and provides a
search() method that will return the data provider.
To add search capability for the Post model we can create PostSearch like
in the following example:
<?php
namespace appmodels;
use Yii;
use yiibaseModel;
use yiidataActiveDataProvider;
class PostSearch extends Post
{
public function rules()
{
// only fields in rules() are searchable
return [
[[’id’], ’integer’],
[[’title’, ’creation_date’], ’safe’],
];
}
8.3. DATA WIDGETS 297
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
public function search($params)
{
$query = Post::find();
$dataProvider = new ActiveDataProvider([
’query’ => $query,
]);
// load the seach form data and validate
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
// adjust the query by adding the filters
$query->andFilterWhere([’id’ => $this->id]);
$query->andFilterWhere([’like’, ’title’, $this->name])
->andFilterWhere([’like’, ’creation_date’, $this->
creation_date]);
return $dataProvider;
}
}
You can use this function in the controller to get the dataProvider for the
GridView:
$searchModel = new PostSearch();
$dataProvider = $searchModel->search(Yii::$app->request->get());
return $this->render(’myview’, [
’dataProvider’ => $dataProvider,
’searchModel’ => $searchModel,
]);
And in the view you then assign the $dataProvider and $searchModel to the
GridView:
echo GridView::widget([
’dataProvider’ => $dataProvider,
’filterModel’ => $searchModel,
]);
Working with model relations
When displaying active records in a GridView you might encounter the case
where you display values of related columns such as the post’s author’s name
instead of just his id. You do this by defining the attribute name in columns
298 CHAPTER 8. DISPLAYING DATA
as author.name when the Post model has a relation named author and the
author model has an attribute name. The GridView will then display the
name of the author but sorting and filtering are not enabled by default. You
have to adjust the PostSearch model that has been introduced in the last
section to add this functionality.
To enable sorting on a related column you have to join the related table
and add the sorting rule to the Sort component of the data provider:
$query = Post::find();
$dataProvider = new ActiveDataProvider([
’query’ => $query,
]);
// join with relation ‘author‘ that is a relation to the table ‘users‘
// and set the table alias to be ‘author‘
$query->joinWith([’author’ => function($query) { $query->from([’author’ => ’
users’]); }]);
// enable sorting for the related column
$dataProvider->sort->attributes[’author.name’] = [
’asc’ => [’author.name’ => SORT_ASC],
’desc’ => [’author.name’ => SORT_DESC],
];
// ...
Filtering also needs the joinWith call as above. You also need to define the
searchable column in attributes and rules like this:
public function attributes()
{
// add related fields to searchable attributes
return array_merge(parent::attributes(), [’author.name’]);
}
public function rules()
{
return [
[[’id’], ’integer’],
[[’title’, ’creation_date’, ’author.name’], ’safe’],
];
}
In search() you then just add another filter condition with:
$query->andFilterWhere([’LIKE’, ’author.name’, $this->getAttribute(’author.
name’)]);
Info: In the above we use the same string for the relation name
and the table alias, however when your alias and relation name
differ, you have to pay attention on where to use the alias and
where to use the relation name. A simple rule for this is to use
the alias in every place that is used to build the database query
8.3. DATA WIDGETS 299
and the relation name in all other definitions like in attributes()
and rules() etc.
For example you use the alias au for the author relation table,
the joinWith statement looks like the following:
$query->joinWith([’author’ => function($query) { $query->from([’
au’ => ’users’]); }]);
It is also possible to just call $query->joinWith([’author’]); when
the alias is defined in the relation definition.
The alias has to be used in the filter condition but the attribute
name stays the same:
$query->andFilterWhere([’LIKE’, ’au.name’, $this->getAttribute(’
author.name’)]);
Same is true for the sorting definition:
$dataProvider->sort->attributes[’author.name’] = [
’asc’ => [’au.name’ => SORT_ASC],
’desc’ => [’au.name’ => SORT_DESC],
];
Also when specifying the yiidataSort::defaultOrder for sort-
ing you need to use the relation name instead of the alias:
$dataProvider->sort->defaultOrder = [’author.name’ => SORT_ASC];
Info: For more information on joinWith and the queries performed
in the background, check the active record docs on eager and lazy
loading.
Using sql views for filtering, sorting and displaying data There is
also other approach that can be faster and more useful - sql views. So for
example if we need to show gridview with users and their profiles we can do
it in this way:
CREATE OR REPLACE VIEW vw_user_info AS
SELECT user.*, user_profile.lastname, user_profile.firstname
FROM user, user_profile
WHERE user.id = user_profile.user_id
Then you need to create ActiveRecord that will be representing this view:
namespace appmodelsviewsgrid;
use yiidbActiveRecord;
class UserView extends ActiveRecord
{
300 CHAPTER 8. DISPLAYING DATA
/**
* @inheritdoc
*/
public static function tableName()
{
return ’vw_user_info’;
}
public static function primaryKey()
{
return [’id’];
}
/**
* @inheritdoc
*/
public function rules()
{
return [
// define here your rules
];
}
/**
* @inheritdoc
*/
public static function attributeLabels()
{
return [
// define here your attribute labels
];
}
}
After that you can youse this UserView active record with search models,
without additional specifying of sorting and filtering attributes. All attrib-
utes will be working out of the box. Note that this approach has several pros
and cons:
• you dont need to specify different sorting and filtering conditions and
other things. Everything works out of the box;
• it can be much faster because of data size, count of sql queries per-
formed (for each relation you will need additional query);
• since this is a just simple mupping UI on sql view it lacks of some
domain logic that is in your entities, so if you will have some methods
like isActive, isDeleted or other that will influence on UI you will need
to duplicate them in this class too.
8.4. WORKING WITH CLIENT SCRIPTS 301
Multiple GridViews on one page
You can use more than one GridView on a single page but some additional
configuration is needed so that they do not interfere. When using multiple
instances of GridView you have to configure different parameter names for
the generated sort and pagination links so that each GridView has its in-
dividual sorting and pagination. You do so by setting the yiidataSort
::sortParam and yiidataPagination::pageParam of the dataProviders
yiidataBaseDataProvider::$sort and yiidataBaseDataProvider::
$pagination instance.
Assume we want to list Post and User models for which we have already
prepared two data providers in $userProvider and $postProvider:
use yiigridGridView;
$userProvider->pagination->pageParam = ’user-page’;
$userProvider->sort->sortParam = ’user-sort’;
$postProvider->pagination->pageParam = ’post-page’;
$postProvider->sort->sortParam = ’post-sort’;
echo ’<h1>Users</h1>’;
echo GridView::widget([
’dataProvider’ => $userProvider,
]);
echo ’<h1>Posts</h1>’;
echo GridView::widget([
’dataProvider’ => $postProvider,
]);
Using GridView with Pjax
TBD
8.4 Working with Client Scripts
Note: This section is under development.
Registering scripts
With the yiiwebView object you can register scripts. There are two ded-
icated methods for it: yiiwebView::registerJs() for inline scripts and
yiiwebView::registerJsFile() for external scripts. Inline scripts are
useful for configuration and dynamically generated code. The method for
adding these can be used as follows:
$this->registerJs("var options = ".json_encode($options).";", View::POS_END,
’my-options’);
302 CHAPTER 8. DISPLAYING DATA
The first argument is the actual JS code we want to insert into the page.
The second argument determines where script should be inserted into the
page. Possible values are:
• yiiwebView::POS_HEAD for head section.
• yiiwebView::POS_BEGIN for right after opening <body>.
• yiiwebView::POS_END for right before closing </body>.
• yiiwebView::POS_READY for executing code on document ready event.
This will register yiiwebJqueryAsset automatically.
• yiiwebView::POS_LOAD for executing code on document load event.
This will register yiiwebJqueryAsset automatically.
The last argument is a unique script ID that is used to identify code block
and replace existing one with the same ID instead of adding a new one. If
you don’t provide it, the JS code itself will be used as the ID.
An external script can be added like the following:
$this->registerJsFile(’http://example.com/js/main.js’, [’depends’ => [
JqueryAsset::className()]]);
The arguments for yiiwebView::registerJsFile() are similar to those
for yiiwebView::registerCssFile(). In the above example, we register
the main.js file with the dependency on JqueryAsset. This means the main.js
file will be added AFTER jquery.js. Without this dependency specification,
the relative order between main.js and jquery.js would be undefined.
Like for yiiwebView::registerCssFile(), it is also highly recom-
mended that you use asset bundles to register external JS files rather than
using yiiwebView::registerJsFile().
Registering asset bundles
As was mentioned earlier it’s preferred to use asset bundles instead of using
CSS and JavaScript directly. You can get details on how to define asset
bundles in asset manager section of the guide. As for using already defined
asset bundle, it’s very straightforward:
frontendassetsAppAsset::register($this);
Registering CSS
You can register CSS using yiiwebView::registerCss() or yiiwebView
::registerCssFile(). The former registers a block of CSS code while the
latter registers an external CSS file. For example,
$this->registerCss("body { background: #f00; }");
8.5. THEMING 303
The code above will result in adding the following to the head section of the
page:
<style>
body { background: #f00; }
</style>
If you want to specify additional properties of the style tag, pass an array
of name-values to the third argument. If you need to make sure there’s
only a single style tag use fourth argument as was mentioned in meta tags
description.
$this->registerCssFile("http://example.com/css/themes/black-and-white.css",
[
’depends’ => [BootstrapAsset::className()],
’media’ => ’print’,
], ’css-print-theme’);
The code above will add a link to CSS file to the head section of the page.
• The first argument specifies the CSS file to be registered.
• The second argument specifies the HTML attributes for the resulting
<link> tag. The option depends is specially handled. It specifies which
asset bundles this CSS file depends on. In this case, the dependent asset
bundle is yiibootstrapBootstrapAsset. This means the CSS file
will be added after the CSS files in yiibootstrapBootstrapAsset.
• The last argument specifies an ID identifying this CSS file. If it is not
provided, the URL of the CSS file will be used instead.
It is highly recommended that you use asset bundles to register external CSS
files rather than using yiiwebView::registerCssFile(). Using asset
bundles allows you to combine and compress multiple CSS files, which is
desirable for high traffic websites.
8.5 Theming
Note: This section is under development.
A theme is a directory of view and layout files. Each file of the theme over-
rides corresponding file of an application when rendered. A single application
may use multiple themes and each may provide totally different experience.
At any time only one theme can be active.
Note: Themes usually do not meant to be redistributed since
views are too application specific. If you want to redistribute
customized look and feel consider CSS and JavaScript files in
form of asset bundles instead.
304 CHAPTER 8. DISPLAYING DATA
8.5.1 Configuring a theme
Theme configuration is specified via view component of the application. In
order to set up a theme to work with basic application views the following
should be in your application config file:
’components’ => [
’view’ => [
’theme’ => [
’pathMap’ => [’@app/views’ => ’@app/themes/basic’],
’baseUrl’ => ’@web/themes/basic’,
],
],
],
In the above pathMap defines a map of original paths to themed paths while
baseUrl defines base URL for resources referenced from theme files.
In our case pathMap is [’@app/views’ => ’@app/themes/basic’]. That means
that every view in @app/views will be first searched under @app/themes/basic
and if a view exists in the theme directory it will be used instead of the
original view.
For example, with a configuration above a themed version of a view file
@app/views/site/index.php will be @app/themes/basic/site/index.php. It basic-
ally replaces @app/views in @app/views/site/index.php with @app/themes/basic.
Theming modules
In order to theme modules pathMap may look like the following:
’components’ => [
’view’ => [
’theme’ => [
’pathMap’ => [
’@app/views’ => ’@app/themes/basic’,
’@app/modules’ => ’@app/themes/basic/modules’, // <-- !!!
],
],
],
],
It will allow you to theme @app/modules/blog/views/comment/index.php with @app
/themes/basic/modules/blog/views/comment/index.php.
Theming widgets
In order to theme a widget view located at @app/widgets/currency/views/index
.php you need the following config for view component theme:
’components’ => [
’view’ => [
’theme’ => [
’pathMap’ => [’@app/widgets’ => ’@app/themes/basic/widgets’],
8.5. THEMING 305
],
],
],
With the config above you can create themed version of @app/widgets/currency
/index.php view in @app/themes/basic/widgets/currency/index.php.
8.5.2 Using multiple paths
It is possible to map a single path to multiple theme paths. For example,
’pathMap’ => [
’@app/views’ => [
’@app/themes/christmas’,
’@app/themes/basic’,
],
]
In this case, the view will be searched in @app/themes/christmas/site/index.php
then if it’s not found it will check @app/themes/basic/site/index.php. If there’s
no view there as well application view will be used.
This ability is especially useful if you want to temporary or conditionally
override some views.
306 CHAPTER 8. DISPLAYING DATA
Chapter 9
Security
9.1 Authentication
Note: This section is under development.
Authentication is the act of verifying who a user is, and is the basis of the lo-
gin process. Typically, authentication uses the combination of an identifier–a
username or email address–and a password. The user submits these values
through a form, and the application then compares the submitted informa-
tion against that previously stored (e.g., upon registration).
In Yii, this entire process is performed semi-automatically, leaving the
developer to merely implement yiiwebIdentityInterface, the most im-
portant class in the authentication system. Typically, implementation of
IdentityInterface is accomplished using the User model.
You can find a fully featured example of authentication in the advanced
application template. Below, only the interface methods are listed:
class User extends ActiveRecord implements IdentityInterface
{
// ...
/**
* Finds an identity by the given ID.
*
* @param string|integer $id the ID to be looked for
* @return IdentityInterface|null the identity object that matches the
given ID.
*/
public static function findIdentity($id)
{
return static::findOne($id);
}
/**
* Finds an identity by the given token.
*
307
308 CHAPTER 9. SECURITY
* @param string $token the token to be looked for
* @return IdentityInterface|null the identity object that matches the
given token.
*/
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne([’access_token’ => $token]);
}
/**
* @return int|string current user ID
*/
public function getId()
{
return $this->id;
}
/**
* @return string current user auth key
*/
public function getAuthKey()
{
return $this->auth_key;
}
/**
* @param string $authKey
* @return boolean if auth key is valid for current user
*/
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
}
Two of the outlined methods are simple: findIdentity is provided with an
ID value and returns a model instance associated with that ID. The getId
method returns the ID itself. Two of the other methods – getAuthKey and
validateAuthKey – are used to provide extra security to the “remember me”
cookie. The getAuthKey method should return a string that is unique for each
user. You can reliably create a unique string using Yii::$app->getSecurity
()->generateRandomString(). It’s a good idea to also save this as part of the
user’s record:
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if ($this->isNewRecord) {
$this->auth_key = Yii::$app->getSecurity()->generateRandomString
();
}
return true;
}
9.2. AUTHORIZATION 309
return false;
}
The validateAuthKey method just needs to compare the $authKey variable,
passed as parameter (itself retrieved from a cookie), with the value fetched
from database.
9.2 Authorization
Note: This section is under development.
Authorization is the process of verifying that a user has enough permission
to do something. Yii provides two authorization methods: Access Control
Filter (ACF) and Role-Based Access Control (RBAC).
9.2.1 Access Control Filter
Access Control Filter (ACF) is a simple authorization method that is best
used by applications that only need some simple access control. As its name
indicates, ACF is an action filter that can be attached to a controller or a
module as a behavior. ACF will check a set of yiifiltersAccessControl
::rules to make sure the current user can access the requested action.
The code below shows how to use ACF which is implemented as yii
filtersAccessControl:
use yiifiltersAccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
’access’ => [
’class’ => AccessControl::className(),
’only’ => [’login’, ’logout’, ’signup’],
’rules’ => [
[
’allow’ => true,
’actions’ => [’login’, ’signup’],
’roles’ => [’?’],
],
[
’allow’ => true,
’actions’ => [’logout’],
’roles’ => [’@’],
],
],
],
];
}
310 CHAPTER 9. SECURITY
// ...
}
In the code above ACF is attached to the site controller as a behavior. This
is the typical way of using an action filter. The only option specifies that the
ACF should only be applied to login, logout and signup actions. The rules
option specifies the yiifiltersAccessRule, which reads as follows:
• Allow all guest (not yet authenticated) users to access ‘login’ and
‘signup’ actions. The roles option contains a question mark ? which is
a special token recognized as “guests”.
• Allow authenticated users to access ‘logout’ action. The @ character is
another special token recognized as authenticated users.
When ACF performs authorization check, it will examine the rules one by one
from top to bottom until it finds a match. The allow value of the matching
rule will then be used to judge if the user is authorized. If none of the rules
matches, it means the user is NOT authorized and ACF will stop further
action execution.
By default, ACF does only of the followings when it determines a user is
not authorized to access the current action:
• If the user is a guest, it will call yiiwebUser::loginRequired(),
which may redirect the browser to the login page.
• If the user is already authenticated, it will throw a yiiwebForbiddenHttpException.
You may customize this behavior by configuring the yiifiltersAccessControl
::denyCallback property:
[
’class’ => AccessControl::className(),
’denyCallback’ => function ($rule, $action) {
throw new Exception(’You are not allowed to access this page’);
}
]
yiifiltersAccessRule support many options. Below is a summary of
the supported options. You may also extend yiifiltersAccessRule to
create your own customized access rule classes.
• yiifiltersAccessRule::allow: specifies whether this is an “allow”
or “deny” rule.
• yiifiltersAccessRule::actions: specifies which actions this rule
matches. This should be an array of action IDs. The comparison is
case-sensitive. If this option is empty or not set, it means the rule
applies to all actions.
9.2. AUTHORIZATION 311
• yiifiltersAccessRule::controllers: specifies which controllers
this rule matches. This should be an array of controller IDs. The
comparison is case-sensitive. If this option is empty or not set, it
means the rule applies to all controllers.
• yiifiltersAccessRule::roles: specifies which user roles that this
rule matches. Two special roles are recognized, and they are checked
via yiiwebUser::isGuest: - ?: matches a guest user (not authen-
ticated yet) - @: matches an authenticated user Using other role names
requires RBAC (to be described in the next section), and yiiweb
User::can() will be called. If this option is empty or not set, it
means this rule applies to all roles.
• yiifiltersAccessRule::ips: specifies which yiiwebRequest::
userIP this rule matches. An IP address can contain the wildcard *
at the end so that it matches IP addresses with the same prefix. For
example, ‘192.168.*‘ matches all IP addresses in the segment ‘192.168.’.
If this option is empty or not set, it means this rule applies to all IP
addresses.
• yiifiltersAccessRule::verbs: specifies which request method (e.g.
GET, POST) this rule matches. The comparison is case-insensitive.
• yiifiltersAccessRule::matchCallback: specifies a PHP callable
that should be called to determine if this rule should be applied.
• yiifiltersAccessRule::denyCallback: specifies a PHP callable
that should be called when this rule will deny the access.
Below is an example showing how to make use of the matchCallback option,
which allows you to write arbitrary access check logic:
use yiifiltersAccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
’access’ => [
’class’ => AccessControl::className(),
’only’ => [’special-callback’],
’rules’ => [
[
’actions’ => [’special-callback’],
’allow’ => true,
’matchCallback’ => function ($rule, $action) {
return date(’d-m’) === ’31-10’;
}
],
312 CHAPTER 9. SECURITY
],
],
];
}
// Match callback called! This page can be accessed only each October 31
st
public function actionSpecialCallback()
{
return $this->render(’happy-halloween’);
}
}
9.2.2 Role based access control (RBAC)
Role-Based Access Control (RBAC) provides a simple yet powerful cent-
ralized access control. Please refer to the Wiki article1 for details about
comparing RBAC with other more traditional access control schemes.
Yii implements a General Hierarchical RBAC, following the NIST RBAC
model2. It provides the RBAC functionality through the yiirbacManagerInterface
application component.
Using RBAC involves two parts of work. The first part is to build up the
RBAC authorization data, and the second part is to use the authorization
data to perform access check in places where it is needed.
To facilitate our description next, we will first introduce some basic
RBAC concepts.
Basic Concepts
A role represents a collection of permissions (e.g. creating posts, updating
posts). A role may be assigned to one or multiple users. To check if a user
has a specified permission, we may check if the user is assigned with a role
that contains that permission.
Associated with each role or permission, there may be a rule. A rule
represents a piece of code that will be executed during access check to de-
termine if the corresponding role or permission applies to the current user.
For example, the “update post” permission may have a rule that checks if the
current user is the post creator. During access checking, if the user is NOT
the post creator, he/she will be considered not having the “update post”
permission.
Both roles and permissions can be organized in a hierarchy. In particular,
a role may consist of other roles or permissions; and a permission may consist
of other permissions. Yii implements a partial order hierarchy which includes
1
http://en.wikipedia.org/wiki/Role-based_access_control
2
http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf
9.2. AUTHORIZATION 313
the more special tree hierarchy. While a role can contain a permission, it is
not true vice versa.
Configuring RBAC Manager
Before we set off to define authorization data and perform access checking,
we need to configure the yiibaseApplication::authManager application
component. Yii provides two types of authorization managers: yiirbac
PhpManager and yiirbacDbManager. The former uses a PHP script file
to store authorization data, while the latter stores authorization data in
database. You may consider using the former if your application does not
require very dynamic role and permission management.
The following code shows how to configure authManager in the application
configuration:
return [
// ...
’components’ => [
’authManager’ => [
’class’ => ’yiirbacPhpManager’,
],
// ...
],
];
The authManager can now be accessed via Yii::$app->authManager.
Tip: By default, yiirbacPhpManager stores RBAC data in
the file @app/data/rbac.php. Sometime you need to create this file
manually.
Building Authorization Data
Building authorization data is all about the following tasks:
• defining roles and permissions;
• establishing relations among roles and permissions;
• defining rules;
• associating rules with roles and permissions;
• assigning roles to users.
Depending on authorization flexibility requirements the tasks above could
be done in different ways.
If your permissions hierarchy doesn’t change at all and you have a fixed
number of users you can create a console command that will initialize au-
thorization data once via APIs offered by authManager:
314 CHAPTER 9. SECURITY
<?php
namespace appcommands;
use Yii;
use yiiconsoleController;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
// add "createPost" permission
$createPost = $auth->createPermission(’createPost’);
$createPost->description = ’Create a post’;
$auth->add($createPost);
// add "updatePost" permission
$updatePost = $auth->createPermission(’updatePost’);
$updatePost->description = ’Update post’;
$auth->add($updatePost);
// add "author" role and give this role the "createPost" permission
$author = $auth->createRole(’author’);
$auth->add($author);
$auth->addChild($author, $createPost);
// add "admin" role and give this role the "updatePost" permission
// as well as the permissions of the "author" role
$admin = $auth->createRole(’admin’);
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
// Assign roles to users. 1 and 2 are IDs returned by
IdentityInterface::getId()
// usually implemented in your User model.
$auth->assign($author, 2);
$auth->assign($admin, 1);
}
}
After executing the command we’ll get the following hierarchy:
9.2. AUTHORIZATION 315
Author can create post, admin can update post and do everything author
can.
316 CHAPTER 9. SECURITY
If your application allows user signup you need to assign roles to these
new users once. For example, in order for all signed up users to become
authors you in advanced application template you need to modify frontend
modelsSignupForm::signup() as follows:
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->save(false);
// the following three lines were added:
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole(’author’);
$auth->assign($authorRole, $user->getId());
return $user;
}
return null;
}
For applications that require complex access control with dynamically up-
dated authorization data, special user interfaces (i.e. admin panel) may need
to be developed using APIs offered by authManager.
Tip: By default, yiirbacPhpManager stores RBAC data in the
file @app/data/rbac.php. Sometimes when you want to make some
minor changes to the RBAC data, you may directly edit this file.
Using Rules
As aforementioned, rules add additional constraint to roles and permissions.
A rule is a class extending from yiirbacRule. It must implement the yii
rbacRule::execute() method. In the hierarchy we’ve created previously
author cannot edit his own post. Let’s fix it. First we need a rule to verify
that the user is the post author:
namespace apprbac;
use yiirbacRule;
/**
* Checks if authorID matches user passed via params
*/
class AuthorRule extends Rule
{
public $name = ’isAuthor’;
9.2. AUTHORIZATION 317
/**
* @param string|integer $user the user ID.
* @param Item $item the role or permission that this rule is associated
with
* @param array $params parameters passed to ManagerInterface::
checkAccess().
* @return boolean a value indicating whether the rule permits the role
or permission it is associated with.
*/
public function execute($user, $item, $params)
{
return isset($params[’post’]) ? $params[’post’]->createdBy == $user
: false;
}
}
The rule above checks if the post is created by $user. We’ll create a special
permission updateOwnPost in the command we’ve used previously:
// add the rule
$rule = new apprbacAuthorRule;
$auth->add($rule);
// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $this->auth->createPermission(’updateOwnPost’);
$updateOwnPost->description = ’Update own post’;
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);
// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);
Now we’ve got the following hierarchy:
318 CHAPTER 9. SECURITY
Access Check
With the authorization data ready, access check is as simple as a call to
the yiirbacManagerInterface::checkAccess() method. Because most
access check is about the current user, for convenience Yii provides a shortcut
method yiiwebUser::can(), which can be used like the following:
if (Yii::$app->user->can(’createPost’)) {
// create post
}
If the current user is Jane with ID=1 we’re starting at createPost and trying
to get to Jane:
9.2. AUTHORIZATION 319
In order to check if user can update post we need to pass an extra para-
meter that is required by the AuthorRule described before:
if (Yii::$app->user->can(’updatePost’, [’post’ => $post])) {
// update post
}
Here’s what happens if current user is John:
320 CHAPTER 9. SECURITY
We’re starting with the updatePost and going through updateOwnPost. In
order to pass it AuthorRule should return true from its execute method. The
method receives its $params from can method call so the value is [’post’
=> $post]. If everything is OK we’re getting to author that is assigned to
John.
In case of Jane it is a bit simpler since she’s an admin:
9.2. AUTHORIZATION 321
Using Default Roles
A default role is a role that is implicitly assigned to all users. The call to yii
rbacManagerInterface::assign() is not needed, and the authorization
data does not contain its assignment information.
A default role is usually associated with a rule which determines if the
role applies to the user being checked.
Default roles are often used in applications which already have some sort
of role assignment. For example, an application may have a “group” column
in its user table to represent which privilege group each user belongs to. If
each privilege group can be mapped to a RBAC role, you can use the default
role feature to automatically assign each user to a RBAC role. Let’s use an
example to show how this can be done.
Assume in the user table, you have a group column which uses 1 to rep-
resent the administrator group and 2 the author group. You plan to have
two RBAC roles admin and author to represent the permissions for these two
322 CHAPTER 9. SECURITY
groups, respectively. You can create set up the RBAC data as follows,
namespace apprbac;
use Yii;
use yiirbacRule;
/**
* Checks if user group matches
*/
class UserGroupRule extends Rule
{
public $name = ’userGroup’;
public function execute($user, $item, $params)
{
if (!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->group;
if ($item->name === ’admin’) {
return $group == 1;
} elseif ($item->name === ’author’) {
return $group == 1 || $group == 2;
}
}
return false;
}
}
$rule = new apprbacUserGroupRule;
$auth->add($rule);
$author = $auth->createRole(’author’);
$author->ruleName = $rule->name;
$auth->add($author);
// ... add permissions as children of $author ...
$admin = $auth->createRole(’admin’);
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... add permissions as children of $admin ...
Note that in the above, because “author” is added as a child of “admin”, when
you implement the execute() method of the rule class, you need to respect
this hierarchy as well. That is why when the role name is “author”, the
execute() method will return true if the user group is either 1 or 2 (meaning
the user is in either “admin” group or “author” group).
Next, configure authManager by listing the two roles in yiirbacBaseManager
::$defaultRoles:
return [
// ...
’components’ => [
’authManager’ => [
9.3. SECURITY 323
’class’ => ’yiirbacPhpManager’,
’defaultRoles’ => [’admin’, ’author’],
],
// ...
],
];
Now if you perform an access check, both of the admin and author roles will
be checked by evaluating the rules associated with them. If the rule returns
true, it means the role applies to the current user. Based on the above rule
implementation, this means if the group value of a user is 1, the admin role
would apply to the user; and if the group value is 2, the author role would
apply.
9.3 Security
Note: This section is under development.
Good security is vital to the health and success of any application. Unfortu-
nately, many developers cut corners when it comes to security, either due to
a lack of understanding or because implementation is too much of a hurdle.
To make your Yii powered application as secure as possible, Yii has included
several excellent and easy to use security features.
9.3.1 Hashing and verifying passwords
Most developers know that passwords cannot be stored in plain text, but
many developers believe it’s still safe to hash passwords using md5 or sha1.
There was a time when using the aforementioned hashing algorithms was
sufficient, but modern hardware makes it possible to reverse such hashes
very quickly using brute force attacks.
In order to provide increased security for user passwords, even in the
worst case scenario (your application is breached), you need to use a hashing
algorithm that is resilient against brute force attacks. The best current choice
is bcrypt. In PHP, you can create a bcrypt hash using the crypt function3. Yii
provides two helper functions which make using crypt to securely generate
and verify hashes easier.
When a user provides a password for the first time (e.g., upon registra-
tion), the password needs to be hashed:
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
The hash can then be associated with the corresponding model attribute, so
it can be stored in the database for later use.
When a user attempts to log in, the submitted password must be verified
against the previously hashed and stored password:
3
http://php.net/manual/en/function.crypt.php
324 CHAPTER 9. SECURITY
if (Yii::$app->getSecurity()->validatePassword($password, $hash)) {
// all good, logging user in
} else {
// wrong password
}
9.3.2 Generating Pseudorandom data
Pseudorandom data is useful in many situations. For example when resetting
a password via email you need to generate a token, save it to the database,
and send it via email to end user which in turn will allow them to prove
ownership of that account. It is very important that this token be unique
and hard to guess, else there is a possibility that attacker can predict the
token’s value and reset the user’s password.
Yii security helper makes generating pseudorandom data simple:
$key = Yii::$app->getSecurity()->generateRandomString();
Note that you need to have the openssl extension installed in order to gen-
erate cryptographically secure random data.
9.3.3 Encryption and decryption
Yii provides convenient helper functions that allow you to encrypt/decrypt
data using a secret key. The data is passed through the encryption function
so that only the person which has the secret key will be able to decrypt it.
For example, we need to store some information in our database but we need
to make sure only the user which has the secret key can view it (even if the
application database is compromised):
// $data and $secretKey are obtained from the form
$encryptedData = Yii::$app->getSecurity()->encrypt($data, $secretKey);
// store $encryptedData to database
Subsequently when user wants to read the data:
// $secretKey is obtained from user input, $encryptedData is from the
database
$data = Yii::$app->getSecurity()->decrypt($encryptedData, $secretKey);
9.3.4 Confirming data integrity
There are situations in which you need to verify that your data hasn’t been
tampered with by a third party or even corrupted in some way. Yii provides
an easy way to confirm data integrity in the form of two helper functions.
Prefix the data with a hash generated from the secret key and data
// $secretKey our application or user secret, $genuineData obtained from a
reliable source
$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey);
9.3. SECURITY 325
Checks if the data integrity has been compromised
// $secretKey our application or user secret, $data obtained from an
unreliable source
$data = Yii::$app->getSecurity()->validateData($data, $secretKey);
todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide
You also can disable CSRF validation per controller and/or action, by
setting its property:
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
public $enableCsrfValidation = false;
public function actionIndex()
{
// CSRF validation will not be applied to this and other actions
}
}
To disable CSRF validation per custom actions you can do:
namespace appcontrollers;
use yiiwebController;
class SiteController extends Controller
{
public function beforeAction($action)
{
// ...set ‘$this->enableCsrfValidation‘ here based on some
conditions...
// call parent method that will check CSRF if such property is true.
return parent::beforeAction($action);
}
}
9.3.5 Securing Cookies
• validation
• httpOnly is default
9.3.6 See also
• Views security
326 CHAPTER 9. SECURITY
Error: not existing file: security-auth-clients.md
9.3. SECURITY 327
Error: not existing file: security-best-practices.md
328 CHAPTER 9. SECURITY
Chapter 10
Caching
10.1 Caching
Caching is a cheap and effective way to improve the performance of a Web
application. By storing relatively static data in cache and serving it from
cache when requested, the application saves the time that would be required
to generate the data from scratch every time.
Caching can occur at different levels and places in a Web application.
On the server side, at the lower level, cache may be used to store basic data,
such as a list of most recent article information fetched from database; and
at the higher level, cache may be used to store fragments or whole of Web
pages, such as the rendering result of the most recent articles. On the client
side, HTTP caching may be used to keep most recently visited page content
in the browser cache.
Yii supports all these caching mechanisms:
• Data caching
• Fragment caching
• Page caching
• HTTP caching
10.2 Data Caching
Data caching is about storing some PHP variable in cache and retrieving
it later from cache. It is also the foundation for more advanced caching
features, such as query caching and page caching.
The following code is a typical usage pattern of data caching, where
$cache refers to a cache component:
329
330 CHAPTER 10. CACHING
// try retrieving $data from cache
$data = $cache->get($key);
if ($data === false) {
// $data is not found in cache, calculate it from scratch
// store $data in cache so that it can be retrieved next time
$cache->set($key, $data);
}
// $data is available here
10.2.1 Cache Components
Data caching relies on the so-called cache components which represent vari-
ous cache storage, such as memory, files, databases.
Cache components are usually registered as application components so
that they can be globally configurable and accessible. The following code
shows how to configure the cache application component to use memcached1
with two cache servers:
’components’ => [
’cache’ => [
’class’ => ’yiicachingMemCache’,
’servers’ => [
[
’host’ => ’server1’,
’port’ => 11211,
’weight’ => 100,
],
[
’host’ => ’server2’,
’port’ => 11211,
’weight’ => 50,
],
],
],
],
You can then access the above cache component using the expression Yii::
$app->cache.
Because all cache components support the same set of APIs, you can
swap the underlying cache component with a different one by reconfiguring
it in the application configuration without modifying the code that uses the
cache. For example, you can modify the above configuration to use yii
cachingApcCache:
’components’ => [
’cache’ => [
1
http://memcached.org/
10.2. DATA CACHING 331
’class’ => ’yiicachingApcCache’,
],
],
Tip: You can register multiple cache application components.
The component named cache is used by default by many cache-
dependent classes (e.g. yiiwebUrlManager).
Supported Cache Storage
Yii supports a wide range of cache storage. The following is a summary:
• yiicachingApcCache: uses PHP APC2 extension. This option can
be considered as the fastest one when dealing with cache for a cent-
ralized thick application (e.g. one server, no dedicated load balancers,
etc.).
• yiicachingDbCache: uses a database table to store cached data. To
use this cache, you must create a table as specified in yiicaching
DbCache::cacheTable.
• yiicachingDummyCache: serves as a cache placeholder which does
no real caching. The purpose of this component is to simplify the
code that needs to check the availability of cache. For example, during
development or if the server doesn’t have actual cache support, you
may configure a cache component to use this cache. When an actual
cache support is enabled, you can switch to use the corresponding
cache component. In both cases, you may use the same code Yii::$app
->cache->get($key) to attempt retrieving data from the cache without
worrying that Yii::$app->cache might be null.
• yiicachingFileCache: uses standard files to store cached data.
This is particular suitable to cache large chunk of data, such as page
content.
• yiicachingMemCache: uses PHP memcache3 and memcached4 ex-
tensions. This option can be considered as the fastest one when dealing
with cache in a distributed applications (e.g. with several servers, load
balancers, etc.)
• yiiredisCache: implements a cache component based on Redis5
key-value store (redis version 2.6.12 or higher is required).
2
http://php.net/manual/en/book.apc.php
3
http://php.net/manual/en/book.memcache.php
4
http://php.net/manual/en/book.memcached.php
5
http://redis.io/
332 CHAPTER 10. CACHING
• yiicachingWinCache: uses PHP WinCache6 (see also7) extension.
• yiicachingXCache: uses PHP XCache8 extension.
• Zend Data Cache9 as the underlying caching medium.
Tip: You may use different cache storage in the same application.
A common strategy is to use memory-based cache storage to store
data that is small but constantly used (e.g. statistical data), and
use file-based or database-based cache storage to store data that
is big and less frequently used (e.g. page content).
10.2.2 Cache APIs
All cache components have the same base class yiicachingCache and thus
support the following APIs:
• yiicachingCache::get(): retrieves a data item from cache with a
specified key. A false value will be returned if the data item is not
found in the cache or is expired/invalidated.
• yiicachingCache::set(): stores a data item identified by a key in
cache.
• yiicachingCache::add(): stores a data item identified by a key in
cache if the key is not found in the cache.
• yiicachingCache::mget(): retrieves multiple data items from cache
with the specified keys.
• yiicachingCache::mset(): stores multiple data items in cache.
Each item is identified by a key.
• yiicachingCache::madd(): stores multiple data items in cache.
Each item is identified by a key. If a key already exists in the cache,
the data item will be skipped.
• yiicachingCache::exists(): returns a value indicating whether
the specified key is found in the cache.
• yiicachingCache::delete(): removes a data item identified by a
key from the cache.
6
http://iis.net/downloads/microsoft/wincache-extension
7
http://php.net/manual/en/book.wincache.php
8
http://xcache.lighttpd.net/
9
http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_
component.htm
10.2. DATA CACHING 333
• yiicachingCache::flush(): removes all data items from the cache.
Some cache storage, such as MemCache, APC, support retrieving multiple
cached values in a batch mode, which may reduce the overhead involved
in retrieving cached data. The APIs yiicachingCache::mget() and yii
cachingCache::madd() are provided to exploit this feature. In case the
underlying cache storage does not support this feature, it will be simulated.
Because yiicachingCache implements ArrayAccess, a cache component
can be used like an array. The followings are some examples:
$cache[’var1’] = $value1; // equivalent to: $cache->set(’var1’, $value1);
$value2 = $cache[’var2’]; // equivalent to: $value2 = $cache->get(’var2’);
Cache Keys
Each data item stored in cache is uniquely identified by a key. When you
store a data item in cache, you have to specify a key for it. Later when you
retrieve the data item from cache, you should provide the corresponding key.
You may use a string or an arbitrary value as a cache key. When a key
is not a string, it will be automatically serialized into a string.
A common strategy of defining a cache key is to include all determining
factors in terms of an array. For example, yiidbSchema uses the following
key to cache schema information about a database table:
[
__CLASS__, // schema class name
$this->db->dsn, // DB connection data source name
$this->db->username, // DB connection login user
$name, // table name
];
As you can see, the key includes all necessary information needed to uniquely
specify a database table.
When the same cache storage is used by different applications, you should
specify a unique cache key prefix for each application to avoid conflicts of
cache keys. This can be done by configuring the yiicachingCache::
keyPrefix property. For example, in the application configuration you can
write the following code:
’components’ => [
’cache’ => [
’class’ => ’yiicachingApcCache’,
’keyPrefix’ => ’myapp’, // a unique cache key prefix
],
],
To ensure interoperability, only alphanumeric characters should be used.
334 CHAPTER 10. CACHING
Cache Expiration
A data item stored in a cache will remain there forever unless it is removed
because of some caching policy enforcement (e.g. caching space is full and
the oldest data are removed). To change this behavior, you can provide an
expiration parameter when calling yiicachingCache::set() to store a
data item. The parameter indicates for how many seconds the data item can
remain valid in the cache. When you call yiicachingCache::get() to
retrieve the data item, if it has passed the expiration time, the method will
return false, indicating the data item is not found in the cache. For example,
// keep the data in cache for at most 45 seconds
$cache->set($key, $data, 45);
sleep(50);
$data = $cache->get($key);
if ($data === false) {
// $data is expired or is not found in the cache
}
Cache Dependencies
Besides expiration setting, cached data item may also be invalidated by
changes of the so-called cache dependencies. For example, yiicaching
FileDependency represents the dependency of a file’s modification time.
When this dependency changes, it means the corresponding file is modified.
As a result, any outdated file content found in the cache should be invalidated
and the yiicachingCache::get() call should return false.
Cache dependencies are represented as objects of yiicachingDependency
descendant classes. When you call yiicachingCache::set() to store a
data item in the cache, you can pass along an associated cache dependency
object. For example,
// Create a dependency on the modification time of file example.txt.
$dependency = new yiicachingFileDependency([’fileName’ => ’example.txt’])
;
// The data will expire in 30 seconds.
// It may also be invalidated earlier if example.txt is modified.
$cache->set($key, $data, 30, $dependency);
// The cache will check if the data has expired.
// It will also check if the associated dependency was changed.
// It will return false if any of these conditions is met.
$data = $cache->get($key);
Below is a summary of the available cache dependencies:
• yiicachingChainedDependency: the dependency is changed if any
of the dependencies on the chain is changed.
10.2. DATA CACHING 335
• yiicachingDbDependency: the dependency is changed if the query
result of the specified SQL statement is changed.
• yiicachingExpressionDependency: the dependency is changed if
the result of the specified PHP expression is changed.
• yiicachingFileDependency: the dependency is changed if the file’s
last modification time is changed.
• yiicachingTagDependency: associates a cached data item with one
or multiple tags. You may invalidate the cached data items with the
specified tag(s) by calling yiicachingTagDependency::invalidate().
10.2.3 Query Caching
Query caching is a special caching feature built on top of data caching. It is
provided to cache the result of database queries.
Query caching requires a yiidbConnection and a valid cache applic-
ation component. The basic usage of query caching is as follows, assuming
$db is a yiidbConnection instance:
$result = $db->cache(function ($db) {
// the result of the SQL query will be served from the cache
// if query caching is enabled and the query result is found in the
cache
return $db->createCommand(’SELECT * FROM customer WHERE id=1’)->queryOne
();
});
Query caching can be used for DAO as well as ActiveRecord.
Info: Some DBMS (e.g. MySQL10) also support query cach-
ing on the DB server side. You may choose to use either query
caching mechanism. The query caching described above has the
advantage that you may specify flexible cache dependencies and
are potentially more efficient.
Configurations
Query caching has three global configurable options through yiidbConnection:
• yiidbConnection::enableQueryCache: whether to turn on or off
query caching. It defaults to true. Note that to effectively turn on
query caching, you also need to have a valid cache, as specified by yii
dbConnection::queryCache.
10
http://dev.mysql.com/doc/refman/5.1/en/query-cache.html
336 CHAPTER 10. CACHING
• yiidbConnection::queryCacheDuration: this represents the num-
ber of seconds that a query result can remain valid in the cache. You
can use 0 to indicate a query result should remain in the cache forever.
This property is the default value used when yiidbConnection::
cache() is called without specifying a duration.
• yiidbConnection::queryCache: this represents the ID of the cache
application component. It defaults to ’cache’. Query caching is en-
abled only if there is a valid cache application component.
Usages
You can use yiidbConnection::cache() if you have multiple SQL queries
that need to take advantage of query caching. The usage is as follows,
$duration = 60; // cache query results for 60 seconds.
$dependency = ...; // optional dependency
$result = $db->cache(function ($db) {
// ... perform SQL queries here ...
return $result;
}, $duration, $dependency);
Any SQL queries in the anonymous function will be cached for the specified
duration with the specified dependency. If the result of a query is found
valid in the cache, the query will be skipped and the result will be served
from the cache instead. If you do not specify the $duration parameter, the
value of yiidbConnection::queryCacheDuration will be used instead.
Sometimes within cache(), you may want to disable query caching for
some particular queries. You can use yiidbConnection::noCache() in
this case.
$result = $db->cache(function ($db) {
// SQL queries that use query caching
$db->noCache(function ($db) {
// SQL queries that do not use query caching
});
// ...
return $result;
});
If you just want to use query caching for a single query, you can call yiidb
Command::cache() when building the command. For example,
10.3. FRAGMENT CACHING 337
// use query caching and set query cache duration to be 60 seconds
$customer = $db->createCommand(’SELECT * FROM customer WHERE id=1’)->cache
(60)->queryOne();
You can also use yiidbCommand::noCache() to disable query caching for
a single command. For example,
$result = $db->cache(function ($db) {
// SQL queries that use query caching
// do not use query caching for this command
$customer = $db->createCommand(’SELECT * FROM customer WHERE id=1’)->
noCache()->queryOne();
// ...
return $result;
});
Limitations
Query caching does not work with query results that contain resource hand-
lers. For example, when using the BLOB column type in some DBMS, the
query result will return a resource handler for the column data.
Some caching storage has size limitation. For example, memcache limits
the maximum size of each entry to be 1MB. Therefore, if the size of a query
result exceeds this limit, the caching will fail.
10.3 Fragment Caching
Fragment caching refers to caching a fragment of a Web page. For example,
if a page displays a summary of yearly sale in a table, you can store this
table in cache to eliminate the time needed to generate this table for each
request. Fragment caching is built on top of data caching.
To use fragment caching, use the following construct in a view:
if ($this->beginCache($id)) {
// ... generate content here ...
$this->endCache();
}
That is, enclose content generation logic in a pair of yiibaseView::
beginCache() and yiibaseView::endCache() calls. If the content is
found in the cache, yiibaseView::beginCache() will render the cached
content and return false, thus skip the content generation logic. Otherwise,
your content generation logic will be called, and when yiibaseView::
338 CHAPTER 10. CACHING
endCache() is called, the generated content will be captured and stored in
the cache.
Like data caching, a unique $id is needed to identify a content cache.
10.3.1 Caching Options
You may specify additional options about fragment caching by passing the
option array as the second parameter to the yiibaseView::beginCache()
method. Behind the scene, this option array will be used to configure a
yiiwidgetsFragmentCache widget which implements the actual fragment
caching functionality.
Duration
Perhaps the most commonly used option of fragment caching is yiiwidgets
FragmentCache::duration. It specifies for how many seconds the content
can remain valid in a cache. The following code caches the content fragment
for at most one hour:
if ($this->beginCache($id, [’duration’ => 3600])) {
// ... generate content here ...
$this->endCache();
}
If the option is not set, it will take the default value 0, which means the
cached content will never expire.
Dependencies
Like data caching, content fragment being cached can also have dependen-
cies. For example, the content of a post being displayed depends on whether
or not the post is modified.
To specify a dependency, set the yiiwidgetsFragmentCache::dependency
option, which can be either an yiicachingDependency object or a config-
uration array for creating a dependency object. The following code specifies
that the fragment content depends on the change of the updated_at column
value:
$dependency = [
’class’ => ’yiicachingDbDependency’,
’sql’ => ’SELECT MAX(updated_at) FROM post’,
];
if ($this->beginCache($id, [’dependency’ => $dependency])) {
// ... generate content here ...
10.3. FRAGMENT CACHING 339
$this->endCache();
}
Variations
Content being cached may be variated according to some parameters. For
example, for a Web application supporting multiple languages, the same
piece of view code may generate the content in different languages. There-
fore, you may want to make the cached content variated according to the
current application language.
To specify cache variations, set the yiiwidgetsFragmentCache::variations
option, which should be an array of scalar values, each representing a par-
ticular variation factor. For example, to make the cached content variated
by the language, you may use the following code:
if ($this->beginCache($id, [’variations’ => [Yii::$app->language]])) {
// ... generate content here ...
$this->endCache();
}
Toggling Caching
Sometimes you may want to enable fragment caching only when certain
conditions are met. For example, for a page displaying a form, you only
want to cache the form when it is initially requested (via GET request).
Any subsequent display (via POST request) of the form should not be cached
because the form may contain user input. To do so, you may set the yii
widgetsFragmentCache::enabled option, like the following:
if ($this->beginCache($id, [’enabled’ => Yii::$app->request->isGet])) {
// ... generate content here ...
$this->endCache();
}
10.3.2 Nested Caching
Fragment caching can be nested. That is, a cached fragment can be enclosed
within another fragment which is also cached. For example, the comments
are cached in an inner fragment cache, and they are cached together with
the post content in an outer fragment cache. The following code shows how
two fragment caches can be nested:
if ($this->beginCache($id1)) {
340 CHAPTER 10. CACHING
// ...content generation logic...
if ($this->beginCache($id2, $options2)) {
// ...content generation logic...
$this->endCache();
}
// ...content generation logic...
$this->endCache();
}
Different caching options can be set for the nested caches. For example, the
inner caches and the outer caches can use different cache duration values.
Even when the data cached in the outer cache is invalidated, the inner cache
may still provide the valid inner fragment. However, it is not true vice versa.
If the outer cache is evaluated to be valid, it will continue to provide the same
cached copy even after the content in the inner cache has been invalidated.
Therefore, you must be careful in setting the durations or the dependencies
of the nested caches, otherwise the outdated inner fragments may be kept in
the outer fragment.
10.3.3 Dynamic Content
When using fragment caching, you may encounter the situation where a large
fragment of content is relatively static except at one or a few places. For
example, a page header may display the main menu bar together with the
name of the current user. Another problem is that the content being cached
may contain PHP code that must be executed for every request (e.g. the
code for registering an asset bundle). Both problems can be solved by the
so-called dynamic content feature.
A dynamic content means a fragment of output that should not be cached
even if it is enclosed within a fragment cache. To make the content dynamic
all the time, it has to be generated by executing some PHP code for every
request, even if the enclosing content is being served from cache.
You may call yiibaseView::renderDynamic() within a cached frag-
ment to insert dynamic content at the desired place, like the following,
if ($this->beginCache($id1)) {
// ...content generation logic...
echo $this->renderDynamic(’return Yii::$app->user->identity->name;’);
// ...content generation logic...
$this->endCache();
10.4. PAGE CACHING 341
}
The yiibaseView::renderDynamic() method takes a piece of PHP code
as its parameter. The return value of the PHP code is treated as the dynamic
content. The same PHP code will be executed for every request, no matter
the enclosing fragment is being served from cached or not.
10.4 Page Caching
Page caching refers to caching the content of a whole page on the server side.
Later when the same page is requested again, its content will be served from
the cache instead of regenerating it from scratch.
Page caching is supported by yiifiltersPageCache, an action filter.
It can be used like the following in a controller class:
public function behaviors()
{
return [
[
’class’ => ’yiifiltersPageCache’,
’only’ => [’index’],
’duration’ => 60,
’variations’ => [
Yii::$app->language,
],
’dependency’ => [
’class’ => ’yiicachingDbDependency’,
’sql’ => ’SELECT COUNT(*) FROM post’,
],
],
];
}
The above code states that page caching should be used only for the index
action; the page content should be cached for at most 60 seconds and should
be variated by the current application language; and the cached page should
be invalidated if the total number of posts is changed.
As you can see, page caching is very similar to fragment caching. They
both support options such as duration, dependencies, variations, and enabled.
Their main difference is that page caching is implemented as an action filter
while fragment caching a widget.
You can use fragment caching as well as dynamic content together with
page caching.
10.5 HTTP Caching
Besides server-side caching that we have described in the previous sections,
Web applications may also exploit client-side caching to save the time for
342 CHAPTER 10. CACHING
generating and transmitting the same page content.
To use client-side caching, you may configure yiifiltersHttpCache
as a filter for controller actions whose rendering result may be cached on the
client side. yiifiltersHttpCache only works for GET and HEAD requests.
It can handle three kinds of cache-related HTTP headers for these requests:
• yiifiltersHttpCache::lastModified
• yiifiltersHttpCache::etagSeed
• yiifiltersHttpCache::cacheControlHeader
10.5.1 Last-Modified Header
The Last-Modified header uses a timestamp to indicate if the page has been
modified since the client caches it.
You may configure the yiifiltersHttpCache::lastModified prop-
erty to enable sending the Last-Modified header. The property should be
a PHP callable returning a UNIX timestamp about the page modification
time. The signature of the PHP callable should be as follows,
/**
* @param Action $action the action object that is being handled currently
* @param array $params the value of the "params" property
* @return integer a UNIX timestamp representing the page modification time
*/
function ($action, $params)
The following is an example of making use of the Last-Modified header:
public function behaviors()
{
return [
[
’class’ => ’yiifiltersHttpCache’,
’only’ => [’index’],
’lastModified’ => function ($action, $params) {
$q = new yiidbQuery();
return $q->from(’post’)->max(’updated_at’);
},
],
];
}
The above code states that HTTP caching should be enabled for the index
action only. It should generate a Last-Modified HTTP header based on the
last update time of posts. When a browser visits the index page for the first
time, the page will be generated on the server and sent to the browser; If the
browser visits the same page again and there is no post being modified during
the period, the server will not re-generate the page, and the browser will use
the cached version on the client side. As a result, server-side rendering and
page content transmission are both skipped.
10.5. HTTP CACHING 343
10.5.2 ETag Header
The “Entity Tag” (or ETag for short) header use a hash to represent the
content of a page. If the page is changed, the hash will be changed as well.
By comparing the hash kept on the client side with the hash generated on
the server side, the cache may determine whether the page has been changed
and should be re-transmitted.
You may configure the yiifiltersHttpCache::etagSeed property to
enable sending the ETag header. The property should be a PHP callable
returning a seed for generating the ETag hash. The signature of the PHP
callable should be as follows,
/**
* @param Action $action the action object that is being handled currently
* @param array $params the value of the "params" property
* @return string a string used as the seed for generating an ETag hash
*/
function ($action, $params)
The following is an example of making use of the ETag header:
public function behaviors()
{
return [
[
’class’ => ’yiifiltersHttpCache’,
’only’ => [’view’],
’etagSeed’ => function ($action, $params) {
$post = $this->findModel(Yii::$app->request->get(’id’));
return serialize([$post->title, $post->content]);
},
],
];
}
The above code states that HTTP caching should be enabled for the view
action only. It should generate an ETag HTTP header based on the title and
content of the requested post. When a browser visits the view page for the
first time, the page will be generated on the server and sent to the browser;
If the browser visits the same page again and there is no change to the title
and content of the post, the server will not re-generate the page, and the
browser will use the cached version on the client side. As a result, server-side
rendering and page content transmission are both skipped.
ETags allow more complex and/or more precise caching strategies than
Last-Modified headers. For instance, an ETag can be invalidated if the site
has switched to another theme.
Expensive ETag generation may defeat the purpose of using HttpCache
and introduce unnecessary overhead, since they need to be re-evaluated on
every request. Try to find a simple expression that invalidates the cache if
the page content has been modified.
344 CHAPTER 10. CACHING
Note: In compliance to RFC 723211, HttpCache will send out both
ETag and Last-Modified headers if they are both configured. And
if the client sends both of the If-None-Match header and the If-
Modified-Since header, only the former will be respected.
10.5.3 Cache-Control Header
The Cache-Control header specifies the general caching policy for pages. You
may send it by configuring the yiifiltersHttpCache::cacheControlHeader
property with the header value. By default, the following header will be sent:
Cache-Control: public, max-age=3600
10.5.4 Session Cache Limiter
When a page uses session, PHP will automatically send some cache-related
HTTP headers as specified in the session.cache_limiter PHP INI setting.
These headers may interfere or disable the caching that you want from
HttpCache. To prevent this problem, by default HttpCache will disable sending
these headers automatically. If you want to change this behavior, you should
configure the yiifiltersHttpCache::sessionCacheLimiter property. The
property can take a string value, including public, private, private_no_expire,
and nocache. Please refer to the PHP manual about session_cache_limiter()12
for explanations about these values.
10.5.5 SEO Implications
Search engine bots tend to respect cache headers. Since some crawlers have
a limit on how many pages per domain they process within a certain time
span, introducing caching headers may help indexing your site as they reduce
the number of pages that need to be processed.
11
http://tools.ietf.org/html/rfc7232#section-2.4
12
http://www.php.net/manual/en/function.session-cache-limiter.php
Chapter 11
RESTful Web Services
11.1 Quick Start
Yii provides a whole set of tools to simplify the task of implementing RESTful
Web Service APIs. In particular, Yii supports the following features about
RESTful APIs:
• Quick prototyping with support for common APIs for Active Record;
• Response format (supporting JSON and XML by default) negotiation;
• Customizable object serialization with support for selectable output
fields;
• Proper formatting of collection data and validation errors;
• Support for HATEOAS1;
• Efficient routing with proper HTTP verb check;
• Built-in support for the OPTIONS and HEAD verbs;
• Authentication and authorization;
• Data caching and HTTP caching;
• Rate limiting;
In the following, we use an example to illustrate how you can build a set of
RESTful APIs with some minimal coding effort.
Assume you want to expose the user data via RESTful APIs. The user
data are stored in the user DB table, and you have already created the yii
dbActiveRecord class appmodelsUser to access the user data.
1
http://en.wikipedia.org/wiki/HATEOAS
345
346 CHAPTER 11. RESTFUL WEB SERVICES
11.1.1 Creating a Controller
First, create a controller class appcontrollersUserController as follows,
namespace appcontrollers;
use yiirestActiveController;
class UserController extends ActiveController
{
public $modelClass = ’appmodelsUser’;
}
The controller class extends from yiirestActiveController. By spe-
cifying yiirestActiveController::modelClass as appmodelsUser, the
controller knows what model can be used for fetching and manipulating
data.
11.1.2 Configuring URL Rules
Then, modify the configuration about the urlManager component in your ap-
plication configuration:
’urlManager’ => [
’enablePrettyUrl’ => true,
’enableStrictParsing’ => true,
’showScriptName’ => false,
’rules’ => [
[’class’ => ’yiirestUrlRule’, ’controller’ => ’user’],
],
]
The above configuration mainly adds a URL rule for the user controller so
that the user data can be accessed and manipulated with pretty URLs and
meaningful HTTP verbs.
11.1.3 Trying it Out
With the above minimal amount of effort, you have already finished your
task of creating the RESTful APIs for accessing the user data. The APIs
you have created include:
• GET /users: list all users page by page;
• HEAD /users: show the overview information of user listing;
• POST /users: create a new user;
• GET /users/123: return the details of the user 123;
• HEAD /users/123: show the overview information of user 123;
11.1. QUICK START 347
• PATCH /users/123 and PUT /users/123: update the user 123;
• DELETE /users/123: delete the user 123;
• OPTIONS /users: show the supported verbs regarding endpoint /users;
• OPTIONS /users/123: show the supported verbs regarding endpoint /users
/123.
Info: Yii will automatically pluralize controller names for use in
endpoints.
You may access your APIs with the curl command like the following,
$ curl -i -H "Accept:application/json" "http://localhost/users"
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Link: <http://localhost/users?page=1>; rel=self,
<http://localhost/users?page=2>; rel=next,
<http://localhost/users?page=50>; rel=last
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
[
{
"id": 1,
...
},
{
"id": 2,
...
},
...
]
Try changing the acceptable content type to be application/xml, and you will
see the result is returned in XML format:
$ curl -i -H "Accept:application/xml" "http://localhost/users"
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
348 CHAPTER 11. RESTFUL WEB SERVICES
X-Pagination-Per-Page: 20
Link: <http://localhost/users?page=1>; rel=self,
<http://localhost/users?page=2>; rel=next,
<http://localhost/users?page=50>; rel=last
Transfer-Encoding: chunked
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<response>
<item>
<id>1</id>
...
</item>
<item>
<id>2</id>
...
</item>
...
</response>
Tip: You may also access your APIs via Web browser by entering
the URL http://localhost/users. However, you may need some
browser plugins to send specific request headers.
As you can see, in the response headers, there are information about the
total count, page count, etc. There are also links that allow you to navigate
to other pages of data. For example, http://localhost/users?page=2 would
give you the next page of the user data.
Using the fields and expand parameters, you may also specify which fields
should be included in the result. For example, the URL http://localhost/
users?fields=id,email will only return the id and email fields.
Info: You may have noticed that the result of http://localhost/
users includes some sensitive fields, such as password_hash, auth_key
. You certainly do not want these to appear in your API result.
You can and should filter out these fields as described in the
Response Formatting section.
11.1.4 Summary
Using the Yii RESTful API framework, you implement an API endpoint in
terms of a controller action, and you use a controller to organize the actions
that implement the endpoints for a single type of resource.
Resources are represented as data models which extend from the yii
baseModel class. If you are working with databases (relational or NoSQL),
it is recommended you use yiidbActiveRecord to represent resources.
You may use yiirestUrlRule to simplify the routing to your API
endpoints.
11.2. RESOURCES 349
While not required, it is recommended that you develop your RESTful
APIs as a separate application, different from your Web front end and back
end for easier maintenance.
11.2 Resources
RESTful APIs are all about accessing and manipulating resources. You may
view resources as models in the MVC paradigm.
While there is no restriction in how to represent a resource, in Yii you
usually would represent resources in terms of objects of yiibaseModel or
its child classes (e.g. yiidbActiveRecord), for the following reasons:
• yiibaseModel implements the yiibaseArrayable interface, which
allows you to customize how you want to expose resource data through
RESTful APIs.
• yiibaseModel supports input validation, which is useful if your
RESTful APIs need to support data input.
• yiidbActiveRecord provides powerful DB data access and manip-
ulation support, which makes it a perfect fit if your resource data is
stored in databases.
In this section, we will mainly describe how a resource class extending from
yiibaseModel (or its child classes) can specify what data may be returned
via RESTful APIs. If the resource class does not extend from yiibase
Model, then all its public member variables will be returned.
11.2.1 Fields
When including a resource in a RESTful API response, the resource needs to
be serialized into a string. Yii breaks this process into two steps. First, the
resource is converted into an array by yiirestSerializer. Second, the
array is serialized into a string in a requested format (e.g. JSON, XML) by
yiiwebResponseFormatterInterface. The first step is what you should
mainly focus when developing a resource class.
By overriding yiibaseModel::fields() and/or yiibaseModel::
extraFields(), you may specify what data, called fields, in the resource
can be put into its array representation. The difference between these two
methods is that the former specifies the default set of fields which should
be included in the array representation, while the latter specifies additional
fields which may be included in the array if an end user requests for them
via the expand query parameter. For example,
350 CHAPTER 11. RESTFUL WEB SERVICES
// returns all fields as declared in fields()
http://localhost/users
// only returns field id and email, provided they are declared in fields()
http://localhost/users?fields=id,email
// returns all fields in fields() and field profile if it is in extraFields
()
http://localhost/users?expand=profile
// only returns field id, email and profile, provided they are in fields()
and extraFields()
http://localhost/users?fields=id,email&expand=profile
Overriding fields()
By default, yiibaseModel::fields() returns all model attributes as fields,
while yiidbActiveRecord::fields() only returns the attributes which
have been populated from DB.
You can override fields() to add, remove, rename or redefine fields. The
return value of fields() should be an array. The array keys are the field
names, and the array values are the corresponding field definitions which
can be either property/attribute names or anonymous functions returning
the corresponding field values. In the special case when a field name is the
same as its defining attribute name, you can omit the array key. For example,
// explicitly list every field, best used when you want to make sure the
changes
// in your DB table or model attributes do not cause your field changes (to
keep API backward compatibility).
public function fields()
{
return [
// field name is the same as the attribute name
’id’,
// field name is "email", the corresponding attribute name is "
email_address"
’email’ => ’email_address’,
// field name is "name", its value is defined by a PHP callback
’name’ => function () {
return $this->first_name . ’ ’ . $this->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent
implementation
// and blacklist some sensitive fields.
public function fields()
{
$fields = parent::fields();
11.2. RESOURCES 351
// remove fields that contain sensitive information
unset($fields[’auth_key’], $fields[’password_hash’], $fields[’
password_reset_token’]);
return $fields;
}
Warning: Because by default all attributes of a model will be
included in the API result, you should examine your data to make
sure they do not contain sensitive information. If there is such
information, you should override fields() to filter them out. In
the above example, we choose to filter out auth_key, password_hash
and password_reset_token.
Overriding extraFields()
By default, yiibaseModel::extraFields() returns nothing, while yii
dbActiveRecord::extraFields() returns the names of the relations that
have been populated from DB.
The return data format of extraFields() is the same as that of fields
(). Usually, extraFields() is mainly used to specify fields whose values are
objects. For example, given the following field declaration,
public function fields()
{
return [’id’, ’email’];
}
public function extraFields()
{
return [’profile’];
}
the request with http://localhost/users?fields=id,email&expand=profile may
return the following JSON data:
[
{
"id": 100,
"email": "100@example.com",
"profile": {
"id": 100,
"age": 30,
}
},
...
]
352 CHAPTER 11. RESTFUL WEB SERVICES
11.2.2 Links
HATEOAS2, an abbreviation for Hypermedia as the Engine of Application
State, promotes that RESTful APIs should return information that allow
clients to discover actions supported for the returned resources. The key of
HATEOAS is to return a set of hyperlinks with relation information when
resource data are served by the APIs.
Your resource classes may support HATEOAS by implementing the yii
webLinkable interface. The interface contains a single method yiiweb
Linkable::getLinks() which should return a list of yiiwebLink. Typ-
ically, you should return at least the self link representing the URL to the
resource object itself. For example,
use yiidbActiveRecord;
use yiiwebLink;
use yiiwebLinkable;
use yiihelpersUrl;
class User extends ActiveRecord implements Linkable
{
public function getLinks()
{
return [
Link::REL_SELF => Url::to([’user/view’, ’id’ => $this->id], true
),
];
}
}
When a User object is returned in a response, it will contain a _links element
representing the links related to the user, for example,
{
"id": 100,
"email": "user@example.com",
// ...
"_links" => [
"self": "https://example.com/users/100"
]
}
11.2.3 Collections
Resource objects can be grouped into collections. Each collection contains a
list of resource objects of the same type.
While collections can be represented as arrays, it is usually more desirable
to represent them as data providers. This is because data providers support
sorting and pagination of resources, which is a commonly needed feature
2
http://en.wikipedia.org/wiki/HATEOAS
11.3. CONTROLLERS 353
for RESTful APIs returning collections. For example, the following action
returns a data provider about the post resources:
namespace appcontrollers;
use yiirestController;
use yiidataActiveDataProvider;
use appmodelsPost;
class PostController extends Controller
{
public function actionIndex()
{
return new ActiveDataProvider([
’query’ => Post::find(),
]);
}
}
When a data provider is being sent in a RESTful API response, yiirest
Serializer will take out the current page of resources and serialize them as
an array of resource objects. Additionally, yiirestSerializer will also
include the pagination information by the following HTTP headers:
• X-Pagination-Total-Count: The total number of resources;
• X-Pagination-Page-Count: The number of pages;
• X-Pagination-Current-Page: The current page (1-based);
• X-Pagination-Per-Page: The number of resources in each page;
• Link: A set of navigational links allowing client to traverse the resources
page by page.
An example may be found in the Quick Start section.
11.3 Controllers
After creating the resource classes and specifying how resource data should
be formatted, the next thing to do is to create controller actions to expose
the resources to end users through RESTful APIs.
Yii provides two base controller classes to simplify your work of creating
RESTful actions: yiirestController and yiirestActiveController.
The difference between these two controllers is that the latter provides a
default set of actions that are specifically designed to deal with resources
represented as Active Record. So if you are using Active Record and are
comfortable with the provided built-in actions, you may consider extending
your controller classes from yiirestActiveController, which will allow
you to create powerful RESTful APIs with minimal code.
354 CHAPTER 11. RESTFUL WEB SERVICES
Both yiirestController and yiirestActiveController provide
the following features, some of which will be described in detail in the next
few sections:
• HTTP method validation;
• Content negotiation and Data formatting;
• Authentication;
• Rate limiting.
yiirestActiveController in addition provides the following features:
• A set of commonly needed actions: index, view, create, update, delete,
options;
• User authorization in regarding to the requested action and resource.
11.3.1 Creating Controller Classes
When creating a new controller class, a convention in naming the control-
ler class is to use the type name of the resource and use singular form.
For example, to serve user information, the controller may be named as
UserController.
Creating a new action is similar to creating an action for a Web applica-
tion. The only difference is that instead of rendering the result using a view
by calling the render() method, for RESTful actions you directly return the
data. The yiirestController::serializer and the yiiwebResponse
will handle the conversion from the original data to the requested format.
For example,
public function actionView($id)
{
return User::findOne($id);
}
11.3.2 Filters
Most RESTful API features provided by yiirestController are imple-
mented in terms of filters. In particular, the following filters will be executed
in the order they are listed:
• yiifiltersContentNegotiator: supports content negotiation, to
be explained in the Response Formatting section;
• yiifiltersVerbFilter: supports HTTP method validation;
11.3. CONTROLLERS 355
• yiifiltersAuthMethod: supports user authentication, to be ex-
plained in the Authentication section;
• yiifiltersRateLimiter: supports rate limiting, to be explained in
the Rate Limiting section.
These named filters are declared in the yiirestController::behaviors()
method. You may override this method to configure individual filters, dis-
able some of them, or add your own filters. For example, if you only want
to use HTTP basic authentication, you may write the following code:
use yiifiltersauthHttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[’authenticator’] = [
’class’ => HttpBasicAuth::className(),
];
return $behaviors;
}
11.3.3 Extending ActiveController
If your controller class extends from yiirestActiveController, you should
set its yiirestActiveController::modelClass property to be the name
of the resource class that you plan to serve through this controller. The class
must extend from yiidbActiveRecord.
Customizing Actions
By default, yiirestActiveController provides the following actions:
• yiirestIndexAction: list resources page by page;
• yiirestViewAction: return the details of a specified resource;
• yiirestCreateAction: create a new resource;
• yiirestUpdateAction: update an existing resource;
• yiirestDeleteAction: delete the specified resource;
• yiirestOptionsAction: return the supported HTTP methods.
All these actions are declared through the yiirestActiveController::
actions() method. You may configure these actions or disable some of them
by overriding the actions() method, like shown the following,
356 CHAPTER 11. RESTFUL WEB SERVICES
public function actions()
{
$actions = parent::actions();
// disable the "delete" and "create" actions
unset($actions[’delete’], $actions[’create’]);
// customize the data provider preparation with the "prepareDataProvider
()" method
$actions[’index’][’prepareDataProvider’] = [$this, ’prepareDataProvider’
];
return $actions;
}
public function prepareDataProvider()
{
// prepare and return a data provider for the "index" action
}
Please refer to the class references for individual action classes to learn what
configuration options are available.
Performing Access Check
When exposing resources through RESTful APIs, you often need to check if
the current user has the permission to access and manipulate the requested
resource(s). With yiirestActiveController, this can be done by over-
riding the yiirestActiveController::checkAccess() method like the
following,
/**
* Checks the privilege of the current user.
*
* This method should be overridden to check whether the current user has
the privilege
* to run the specified action against the specified data model.
* If the user does not have access, a [[ForbiddenHttpException]] should be
thrown.
*
* @param string $action the ID of the action to be executed
* @param yiibaseModel $model the model to be accessed. If null, it means
no specific model is being accessed.
* @param array $params additional parameters
* @throws ForbiddenHttpException if the user does not have access
*/
public function checkAccess($action, $model = null, $params = [])
{
// check if the user can access $action and $model
// throw ForbiddenHttpException if access should be denied
}
11.4. ROUTING 357
The checkAccess() method will be called by the default actions of yiirest
ActiveController. If you create new actions and also want to perform
access check, you should call this method explicitly in the new actions.
Tip: You may implement checkAccess() by using the Role-Based
Access Control (RBAC) component.
11.4 Routing
With resource and controller classes ready, you can access the resources using
the URL like http://localhost/index.php?r=user/create, similar to what you
can do with normal Web applications.
In practice, you usually want to enable pretty URLs and take advantage
of HTTP verbs. For example, a request POST /users would mean accessing
the user/create action. This can be done easily by configuring the urlManager
application component in the application configuration like the following:
’urlManager’ => [
’enablePrettyUrl’ => true,
’enableStrictParsing’ => true,
’showScriptName’ => false,
’rules’ => [
[’class’ => ’yiirestUrlRule’, ’controller’ => ’user’],
],
]
Compared to the URL management for Web applications, the main new
thing above is the use of yiirestUrlRule for routing RESTful API re-
quests. This special URL rule class will create a whole set of child URL
rules to support routing and URL creation for the specified controller(s).
For example, the above code is roughly equivalent to the following rules:
[
’PUT,PATCH users/<id>’ => ’user/update’,
’DELETE users/<id>’ => ’user/delete’,
’GET,HEAD users/<id>’ => ’user/view’,
’POST users’ => ’user/create’,
’GET,HEAD users’ => ’user/index’,
’users/<id>’ => ’user/options’,
’users’ => ’user/options’,
]
And the following API endpoints are supported by this rule:
• GET /users: list all users page by page;
• HEAD /users: show the overview information of user listing;
• POST /users: create a new user;
358 CHAPTER 11. RESTFUL WEB SERVICES
• GET /users/123: return the details of the user 123;
• HEAD /users/123: show the overview information of user 123;
• PATCH /users/123 and PUT /users/123: update the user 123;
• DELETE /users/123: delete the user 123;
• OPTIONS /users: show the supported verbs regarding endpoint /users;
• OPTIONS /users/123: show the supported verbs regarding endpoint /users
/123.
You may configure the only and except options to explicitly list which actions
to support or which actions should be disabled, respectively. For example,
[
’class’ => ’yiirestUrlRule’,
’controller’ => ’user’,
’except’ => [’delete’, ’create’, ’update’],
],
You may also configure patterns or extraPatterns to redefine existing patterns
or add new patterns supported by this rule. For example, to support a new
action search by the endpoint GET /users/search, configure the extraPatterns
option as follows,
[
’class’ => ’yiirestUrlRule’,
’controller’ => ’user’,
’extraPatterns’ => [
’GET search’ => ’search’,
],
You may have noticed that the controller ID user appears in plural form as
users in the endpoints. This is because yiirestUrlRule automatically
pluralizes controller IDs for them to use in endpoints. You may disable this
behavior by setting yiirestUrlRule::pluralize to be false, or if you
want to use some special names you may configure the yiirestUrlRule
::controller property.
11.5 Response Formatting
When handling a RESTful API request, an application usually takes the
following steps that are related with response formatting:
1. Determine various factors that may affect the response format, such
as media type, language, version, etc. This process is also known as
content negotiation3.
3
http://en.wikipedia.org/wiki/Content_negotiation
11.5. RESPONSE FORMATTING 359
2. Convert resource objects into arrays, as described in the Resources
section. This is done by yiirestSerializer.
3. Convert arrays into a string in the format as determined by the content
negotiation step. This is done by yiiwebResponseFormatterInterface
registered with the yiiwebResponse::formatters application com-
ponent.
11.5.1 Content Negotiation
Yii supports content negotiation via the yiifiltersContentNegotiator
filter. The RESTful API base controller class yiirestController is
equipped with this filter under the name of contentNegotiator. The filer
provides response format negotiation as well as language negotiation. For
example, if a RESTful API request contains the following header,
Accept: application/json; q=1.0, */*; q=0.1
it will get a response in JSON format, like the following:
$ curl -i -H "Accept: application/json; q=1.0, */*; q=0.1" "http://localhost
/users"
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Link: <http://localhost/users?page=1>; rel=self,
<http://localhost/users?page=2>; rel=next,
<http://localhost/users?page=50>; rel=last
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
[
{
"id": 1,
...
},
{
"id": 2,
...
},
...
]
Behind the scene, before a RESTful API controller action is executed, the
yiifiltersContentNegotiator filter will check the Accept HTTP header
in the request and set the yiiwebResponse::format to be ’json’. After
360 CHAPTER 11. RESTFUL WEB SERVICES
the action is executed and returns the resulting resource object or collection,
yiirestSerializer will convert the result into an array. And finally, yii
webJsonResponseFormatter will serialize the array into a JSON string
and include it in the response body.
By default, RESTful APIs support both JSON and XML formats. To
support a new format, you should configure the yiifiltersContentNegotiator
::formats property of the contentNegotiator filter like the following in your
API controller classes:
use yiiwebResponse;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[’contentNegotiator’][’formats’][’text/html’] = Response::
FORMAT_HTML;
return $behaviors;
}
The keys of the formats property are the supported MIME types, while the
values are the corresponding response format names which must be suppor-
ted in yiiwebResponse::formatters.
11.5.2 Data Serializing
As we have described above, yiirestSerializer is the central piece re-
sponsible for converting resource objects or collections into arrays. It recog-
nizes objects implementing yiibaseArrayableInterface as well as yii
dataDataProviderInterface. The former is mainly implemented by re-
source objects, while the latter resource collections.
You may configure the serializer by setting the yiirestController::
serializer property with a configuration array. For example, sometimes
you may want to help simplify the client development work by including
pagination information directly in the response body. To do so, configure
the yiirestSerializer::collectionEnvelope property as follows:
use yiirestActiveController;
class UserController extends ActiveController
{
public $modelClass = ’appmodelsUser’;
public $serializer = [
’class’ => ’yiirestSerializer’,
’collectionEnvelope’ => ’items’,
];
}
You may then get the following response for request http://localhost/users:
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
11.6. AUTHENTICATION 361
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
X-Powered-By: PHP/5.4.20
X-Pagination-Total-Count: 1000
X-Pagination-Page-Count: 50
X-Pagination-Current-Page: 1
X-Pagination-Per-Page: 20
Link: <http://localhost/users?page=1>; rel=self,
<http://localhost/users?page=2>; rel=next,
<http://localhost/users?page=50>; rel=last
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
{
"items": [
{
"id": 1,
...
},
{
"id": 2,
...
},
...
],
"_links": {
"self": "http://localhost/users?page=1",
"next": "http://localhost/users?page=2",
"last": "http://localhost/users?page=50"
},
"_meta": {
"totalCount": 1000,
"pageCount": 50,
"currentPage": 1,
"perPage": 20
}
}
11.6 Authentication
Unlike Web applications, RESTful APIs are usually stateless, which means
sessions or cookies should not be used. Therefore, each request should come
with some sort of authentication credentials because the user authentication
status may not be maintained by sessions or cookies. A common practice
is to send a secret access token with each request to authenticate the user.
Since an access token can be used to uniquely identify and authenticate a
user, API requests should always be sent via HTTPS to prevent
from man-in-the-middle (MitM) attacks.
There are different ways to send an access token:
362 CHAPTER 11. RESTFUL WEB SERVICES
• HTTP Basic Auth4: the access token is sent as the username. This
is should only be used when an access token can be safely stored on
the API consumer side. For example, the API consumer is a program
running on a server.
• Query parameter: the access token is sent as a query parameter in the
API URL, e.g., https://example.com/users?access-token=xxxxxxxx. Be-
cause most Web servers will keep query parameters in server logs, this
approach should be mainly used to serve JSONP requests which cannot
use HTTP headers to send access tokens.
• OAuth 25: the access token is obtained by the consumer from an
authorization server and sent to the API server via HTTP Bearer
Tokens6, according to the OAuth2 protocol.
Yii supports all of the above authentication methods. You can also easily
create new authentication methods.
To enable authentication for your APIs, do the following steps:
1. Configure the yiiwebUser::enableSession property of the user ap-
plication component to be false.
2. Specify which authentication methods you plan to use by configuring
the authenticator behavior in your REST controller classes.
3. Implement yiiwebIdentityInterface::findIdentityByAccessToken()
in your yiiwebUser::identityClass.
Step 1 is not required but is recommended for RESTful APIs which should
be stateless. When yiiwebUser::enableSession is false, the user authen-
tication status will NOT be persisted across requests using sessions. Instead,
authentication will be performed for every request, which is accomplished by
Step 2 and 3.
Tip: You may configure yiiwebUser::enableSession of the
user application component in application configurations if you
are developing RESTful APIs in terms of an application. If you
develop RESTful APIs as a module, you may put the follow-
ing line in the module’s init() method, like the following: ‘php
public function init() {
parent::init();
Yii::$app->user->enableSession = false;
4
http://en.wikipedia.org/wiki/Basic_access_authentication
5
http://oauth.net/2/
6
http://tools.ietf.org/html/rfc6750
11.6. AUTHENTICATION 363
} ‘
For example, to use HTTP Basic Auth, you may configure authenticator as
follows,
use yiifiltersauthHttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[’authenticator’] = [
’class’ => HttpBasicAuth::className(),
];
return $behaviors;
}
If you want to support all three authentication methods explained above,
you can use CompositeAuth like the following,
use yiifiltersauthCompositeAuth;
use yiifiltersauthHttpBasicAuth;
use yiifiltersauthHttpBearerAuth;
use yiifiltersauthQueryParamAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[’authenticator’] = [
’class’ => CompositeAuth::className(),
’authMethods’ => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
];
return $behaviors;
}
Each element in authMethods should be an auth method class name or a
configuration array.
Implementation of findIdentityByAccessToken() is application specific. For
example, in simple scenarios when each user can only have one access token,
you may store the access token in an access_token column in the user table.
The method can then be readily implemented in the User class as follows,
use yiidbActiveRecord;
use yiiwebIdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne([’access_token’ => $token]);
}
}
364 CHAPTER 11. RESTFUL WEB SERVICES
After authentication is enabled as described above, for every API request,
the requested controller will try to authenticate the user in its beforeAction()
step.
If authentication succeeds, the controller will perform other checks (such
as rate limiting, authorization) and then run the action. The authenticated
user identity information can be retrieved via Yii::$app->user->identity.
If authentication fails, a response with HTTP status 401 will be sent back
together with other appropriate headers (such as a WWW-Authenticate header
for HTTP Basic Auth).
11.6.1 Authorization
After a user is authenticated, you probably want to check if he or she has the
permission to perform the requested action for the requested resource. This
process is called authorization which is covered in detail in the Authorization
section.
If your controllers extend from yiirestActiveController, you may
override the yiirestController::checkAccess() method to perform au-
thorization check. The method will be called by the built-in actions provided
by yiirestActiveController.
11.7 Rate Limiting
To prevent abuse, you should consider adding rate limiting to your APIs.
For example, you may want to limit the API usage of each user to be at
most 100 API calls within a period of 10 minutes. If too many requests are
received from a user within the stated period of the time, a response with
status code 429 (meaning “Too Many Requests”) should be returned.
To enable rate limiting, the yiiwebUser::identityClass should im-
plement yiifiltersRateLimitInterface. This interface requires imple-
mentation of three methods:
• getRateLimit(): returns the maximum number of allowed requests and
the time period (e.g., [100, 600] means there can be at most 100 API
calls within 600 seconds).
• loadAllowance(): returns the number of remaining requests allowed
and the corresponding UNIX timestamp when the rate limit was last
checked.
• saveAllowance(): saves both the number of remaining requests allowed
and the current UNIX timestamp.
You may want to use two columns in the user table to record the allow-
ance and timestamp information. With those defined, then loadAllowance()
11.8. VERSIONING 365
and saveAllowance() can be implemented to read and save the values of the
two columns corresponding to the current authenticated user. To improve
performance, you may also consider storing these pieces of information in a
cache or NoSQL storage.
Once the identity class implements the required interface, Yii will auto-
matically use yiifiltersRateLimiter configured as an action filter for
yiirestController to perform rate limiting check. The rate limiter will
throw a yiiwebTooManyRequestsHttpException when the rate limit is
exceeded.
You may configure the rate limiter as follows in your REST controller
classes:
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[’rateLimiter’][’enableRateLimitHeaders’] = false;
return $behaviors;
}
When rate limiting is enabled, by default every response will be sent with the
following HTTP headers containing the current rate limiting information:
• X-Rate-Limit-Limit, the maximum number of requests allowed with a
time period
• X-Rate-Limit-Remaining, the number of remaining requests in the current
time period
• X-Rate-Limit-Reset, the number of seconds to wait in order to get the
maximum number of allowed requests
You may disable these headers by configuring yiifiltersRateLimiter::
enableRateLimitHeaders to be false, as shown in the above code example.
11.8 Versioning
A good API is versioned: changes and new features are implemented in new
versions of the API instead of continually altering just one version. Unlike
Web applications, with which you have full control of both the client-side
and server-side code, APIs are meant to be used by clients beyond your
control. For this reason, backward compatibility (BC) of the APIs should be
maintained whenever possible. If a change that may break BC is necessary,
you should introduce it in new version of the API, and bump up the version
number. Existing clients can continue to use the old, working version of the
API; and new or upgraded clients can get the new functionality in the new
API version.
366 CHAPTER 11. RESTFUL WEB SERVICES
Tip: Refer to Semantic Versioning7 for more information on
designing API version numbers.
One common way to implement API versioning is to embed the version
number in the API URLs. For example, http://example.com/v1/users stands
for the /users endpoint of API version 1.
Another method of API versioning, which has gained momentum re-
cently, is to put the version number in the HTTP request headers. This is
typically done through the Accept header:
// via a parameter
Accept: application/json; version=v1
// via a vendor content type
Accept: application/vnd.company.myapp-v1+json
Both methods have their pros and cons, and there are a lot of debates about
each approach. Below you’ll see a practical strategy for API versioning that
is a mix of these two methods:
• Put each major version of API implementation in a separate module
whose ID is the major version number (e.g. v1, v2). Naturally, the API
URLs will contain major version numbers.
• Within each major version (and thus within the corresponding mod-
ule), use the Accept HTTP request header to determine the minor
version number and write conditional code to respond to the minor
versions accordingly.
For each module serving a major version, the module should include the re-
source and controller classes serving that specific version. To better separate
code responsibility, you may keep a common set of base resource and con-
troller classes, and subclass them in each individual version module. Within
the subclasses, implement the concrete code such as Model::fields().
Your code may be organized like the following:
api/
common/
controllers/
UserController.php
PostController.php
models/
User.php
Post.php
modules/
v1/
controllers/
UserController.php
PostController.php
7
http://semver.org/
11.8. VERSIONING 367
models/
User.php
Post.php
v2/
controllers/
UserController.php
PostController.php
models/
User.php
Post.php
Your application configuration would look like:
return [
’modules’ => [
’v1’ => [
’basePath’ => ’@app/modules/v1’,
],
’v2’ => [
’basePath’ => ’@app/modules/v2’,
],
],
’components’ => [
’urlManager’ => [
’enablePrettyUrl’ => true,
’enableStrictParsing’ => true,
’showScriptName’ => false,
’rules’ => [
[’class’ => ’yiirestUrlRule’, ’controller’ => [’v1/user’,
’v1/post’]],
[’class’ => ’yiirestUrlRule’, ’controller’ => [’v2/user’,
’v2/post’]],
],
],
],
];
As a result of the above code, http://example.com/v1/users will return the list
of users in version 1, while http://example.com/v2/users will return version 2
users.
Thanks to modules, the code for different major versions can be well
isolated. But modules make it still possible to reuse code across the modules
via common base classes and other shared resources.
To deal with minor version numbers, you may take advantage of the con-
tent negotiation feature provided by the yiifiltersContentNegotiator
behavior. The contentNegotiator behavior will set the yiiwebResponse::
acceptParams property when it determines which content type to support.
For example, if a request is sent with the HTTP header Accept: application
/json; version=v1, after content negotiation, yiiwebResponse::acceptParams
will contain the value [’version’ => ’v1’].
Based on the version information in acceptParams, you may write condi-
tional code in places such as actions, resource classes, serializers, etc. to
368 CHAPTER 11. RESTFUL WEB SERVICES
provide the appropriate functionality.
Since minor versions by definition require maintaining backward com-
patibility, hopefully there would not be many version checks in your code.
Otherwise, chances are that you may need to create a new major version.
11.9 Error Handling
When handling a RESTful API request, if there is an error in the user
request or if something unexpected happens on the server, you may simply
throw an exception to notify the user that something went wrong. If you can
identify the cause of the error (e.g., the requested resource does not exist),
you should consider throwing an exception along with a proper HTTP status
code (e.g., yiiwebNotFoundHttpException represents a 404 status code).
Yii will send the response along with the corresponding HTTP status code
and text. Yii will also include the serialized representation of the exception
in the response body. For example:
HTTP/1.1 404 Not Found
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
{
"name": "Not Found Exception",
"message": "The requested resource was not found.",
"code": 0,
"status": 404
}
The following list summarizes the HTTP status code that are used by the
Yii REST framework:
• 200: OK. Everything worked as expected.
• 201: A resource was successfully created in response to a POST request.
The Location header contains the URL pointing to the newly created
resource.
• 204: The request was handled successfully and the response contains
no body content (like a DELETE request).
• 304: The resource was not modified. You can use the cached version.
• 400: Bad request. This could be caused by various actions by the user,
such as providing invalid JSON data in the request body, providing
invalid action parameters, etc.
• 401: Authentication failed.
11.9. ERROR HANDLING 369
• 403: The authenticated user is not allowed to access the specified API
endpoint.
• 404: The requested resource does not exist.
• 405: Method not allowed. Please check the Allow header for the allowed
HTTP methods.
• 415: Unsupported media type. The requested content type or version
number is invalid.
• 422: Data validation failed (in response to a POST request, for example).
Please check the response body for detailed error messages.
• 429: Too many requests. The request was rejected due to rate limiting.
• 500: Internal server error. This could be caused by internal program
errors.
11.9.1 Customizing Error Response
Sometimes you may want to customize the default error response format.
For example, instead of relying on using different HTTP statuses to indicate
different errors, you would like to always use 200 as HTTP status and enclose
the actual HTTP status code as part of the JSON structure in the response,
like shown in the following,
HTTP/1.1 200 OK
Date: Sun, 02 Mar 2014 05:31:43 GMT
Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
{
"success": false,
"data": {
"name": "Not Found Exception",
"message": "The requested resource was not found.",
"code": 0,
"status": 404
}
}
To achieve this goal, you can respond to the beforeSend event of the response
component in the application configuration:
return [
// ...
’components’ => [
’response’ => [
’class’ => ’yiiwebResponse’,
’on beforeSend’ => function ($event) {
370 CHAPTER 11. RESTFUL WEB SERVICES
$response = $event->sender;
if ($response->data !== null && !empty(Yii::$app->request->
get[’suppress_response_code’])) {
$response->data = [
’success’ => $response->isSuccessful,
’data’ => $response->data,
];
$response->statusCode = 200;
}
},
],
],
];
The above code will reformat the response (for both successful and failed
responses) as explained when suppress_response_code is passed as a GET para-
meter.
Chapter 12
Development Tools
12.1 Debug toolbar and debugger
Note: This section is under development.
Yii2 includes a handy toolbar, and built-in debugger, for faster development
and debugging of your applications. The toolbar displays information about
the currently opened page, while the debugger can be used to analyze data
you’ve previously collected (i.e., to confirm the values of variables).
Out of the box these tools allow you to:
• Quickly get the framework version, PHP version, response status, cur-
rent controller and action, performance info and more via toolbar
• Browse the application and PHP configuration
• View the request data, request and response headers, session data, and
environment variables
• See, search, and filter the logs
• View any profiling results
• View the database queries executed by the page
• View the emails sent by the application
All of this information will be available per request, allowing you to revisit
the information for past requests as well.
12.1.1 Installing and configuring
To enable these features, add these lines to your configuration file to enable
the debug module:
371
372 CHAPTER 12. DEVELOPMENT TOOLS
’bootstrap’ => [’debug’],
’modules’ => [
’debug’ => ’yiidebugModule’,
]
By default, the debug module only works when browsing the website from
localhost. If you want to use it on a remote (staging) server, add the para-
meter allowedIPs to the configuration to whitelist your IP:
’bootstrap’ => [’debug’],
’modules’ => [
’debug’ => [
’class’ => ’yiidebugModule’,
’allowedIPs’ => [’1.2.3.4’, ’127.0.0.1’, ’::1’]
]
]
If you are using enableStrictParsing URL manager option, add the following
to your rules:
’urlManager’ => [
’enableStrictParsing’ => true,
’rules’ => [
// ...
’debug/<controller>/<action>’ => ’debug/<controller>/<action>’,
],
],
Note: the debugger stores information about each request in the
@runtime/debug directory. If you have problems using The debug-
ger such as weird error messages when using it or the toolbar not
showing up or not showing any requests, check whether the web
server has enough permissions to access this directory and the
files located inside.
Extra configuration for logging and profiling
Logging and profiling are simple but powerful tools that may help you to
understand the execution flow of both the framework and the application.
These tools are useful for development and production environments alike.
While in a production environment, you should log only significantly
important messages manually, as described in logging guide section. It hurts
performance too much to continue to log all messages in production.
In a development environment, the more logging the better, and it’s
especially useful to record the execution trace.
In order to see the trace messages that will help you to understand what
happens under the hood of the framework, you need to set the trace level in
the configuration file:
12.1. DEBUG TOOLBAR AND DEBUGGER 373
return [
// ...
’components’ => [
’log’ => [
’traceLevel’ => YII_DEBUG ? 3 : 0, // <-- here
By default, the trace level is automatically set to 3 if Yii is running in debug
mode, as determined by the presence of the following line in your index.php
file:
defined(’YII_DEBUG’) or define(’YII_DEBUG’, true);
Note: Make sure to disable debug mode in production environ-
ments since it may have a significant and adverse performance
effect. Further, the debug mode may expose sensitive informa-
tion to end users.
12.1.2 Creating your own panels
Both the toolbar and debugger are highly configurable and customizable. To
do so, you can create your own panels that collect and display the specific
data you want. Below we’ll describe the process of creating a simple custom
panel that:
• Collects the views rendered during a request
• Shows the number of views rendered in the toolbar
• Allows you to check the view names in the debugger
The assumption is that you’re using the basic application template.
First we need to implement the Panel class in panels/ViewsPanel.php:
<?php
namespace apppanels;
use yiibaseEvent;
use yiibaseView;
use yiibaseViewEvent;
use yiidebugPanel;
class ViewsPanel extends Panel
{
private $_viewFiles = [];
public function init()
{
parent::init();
Event::on(View::className(), View::EVENT_BEFORE_RENDER, function (
ViewEvent $event) {
374 CHAPTER 12. DEVELOPMENT TOOLS
$this->_viewFiles[] = $event->sender->getViewFile();
});
}
/**
* @inheritdoc
*/
public function getName()
{
return ’Views’;
}
/**
* @inheritdoc
*/
public function getSummary()
{
$url = $this->getUrl();
$count = count($this->data);
return "<div class="yii-debug-toolbar-block"><a href="$url">
Views <span class="label">$count</span></a></div>";
}
/**
* @inheritdoc
*/
public function getDetail()
{
return ’<ol><li>’ . implode(’<li>’, $this->data) . ’</ol>’;
}
/**
* @inheritdoc
*/
public function save()
{
return $this->_viewFiles;
}
}
The workflow for the code above is:
1. init is executed before any controller action is run. This method is the
best place to attach handlers that will collect data during the controller
action’s execution.
2. save is called after controller action is executed. The data returned by
this method will be stored in a data file. If nothing is returned by this
method, the panel won’t be rendered.
3. The data from the data file is loaded into $this->data. For the tool-
bar, this will always represent the latest data, For the debugger, this
12.2. THE GII CODE GENERATION TOOL 375
property may be set to be read from any previous data file as well.
4. The toolbar takes its contents from getSummary. There, we’re showing
the number of view files rendered. The debugger uses getDetail for the
same purpose.
Now it’s time to tell the debugger to use the new panel. In config/web.php,
the debug configuration is modified to:
if (YII_ENV_DEV) {
// configuration adjustments for ’dev’ environment
$config[’bootstrap’][] = ’debug’;
$config[’modules’][’debug’] = [
’class’ => ’yiidebugModule’,
’panels’ => [
’views’ => [’class’ => ’apppanelsViewsPanel’],
],
];
// ...
That’s it. Now we have another useful panel without writing much code.
12.2 The Gii code generation tool
Note: This section is under development.
Yii includes a handy tool, named Gii, that provides rapid prototyping by
generating commonly used code snippets as well as complete CRUD control-
lers.
Gii provides a Web-based interface for you to interactively generate the
code you want. It also provides a command line interface for people who
prefer to work with their console windows most of the time.
12.2.1 Installing and configuring
Gii is an official Yii extension. The preferred way to install this extension is
through composer1.
You can either run this command:
php composer.phar require --prefer-dist yiisoft/yii2-gii "*"
Or you can add this code to the require section of your composer.json file:
"yiisoft/yii2-gii": "*"
Once the Gii extension has been installed, you enable it by adding these lines
to your application configuration file:
1
http://getcomposer.org/download/
376 CHAPTER 12. DEVELOPMENT TOOLS
return [
’bootstrap’ => [’gii’],
’modules’ => [
’gii’ => ’yiigiiModule’,
// ...
],
// ...
];
You can then access Gii through the following URL:
http://localhost/path/to/index.php?r=gii
If you have enabled pretty URLs, you may use the following URL:
http://localhost/path/to/index.php/gii
Note: if you are accessing gii from an IP address other than
localhost, access will be denied by default. To circumvent that
default, add the allowed IP addresses to the configuration:
’gii’ => [
’class’ => ’yiigiiModule’,
’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’
192.168.178.20’] // adjust this to your needs
],
If you have configured Gii similarly in your console application configuration,
you may also access Gii through command window like the following:
# change path to your application’s base path
cd path/to/AppBasePath
# show help information about Gii
yii help gii
# show help information about the model generator in Gii
yii help gii/model
# generate City model from city table
yii gii/model --tableName=city --modelClass=City
Basic application
In basic application template configuration structure is a bit different so Gii
should be configured in config/web.php:
// ...
if (YII_ENV_DEV) {
// configuration adjustments for ’dev’ environment
$config[’bootstrap’][] = ’debug’;
$config[’modules’][’debug’] = ’yiidebugModule’;
12.2. THE GII CODE GENERATION TOOL 377
$config[’bootstrap’][] = ’gii’;
$config[’modules’][’gii’] = ’yiigiiModule’; // <--- here
}
So in order to adjust IP address you need to do it like the following:
if (YII_ENV_DEV) {
// configuration adjustments for ’dev’ environment
$config[’bootstrap’][] = ’debug’;
$config[’modules’][’debug’] = ’yiidebugModule’;
$config[’bootstrap’][] = ’gii’;
$config[’modules’][’gii’] = [
’class’ => ’yiigiiModule’,
’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’192.168.178.20’
],
];
}
12.2.2 How to use it
When you open Gii you first see the entry page that lets you choose a gen-
erator.
By default there are the following generators available:
• Model Generator - This generator generates an ActiveRecord class
for the specified database table.
• CRUD Generator - This generator generates a controller and views
that implement CRUD (Create, Read, Update, Delete) operations for
the specified data model.
378 CHAPTER 12. DEVELOPMENT TOOLS
• Controller Generator - This generator helps you to quickly gener-
ate a new controller class, one or several controller actions and their
corresponding views.
• Form Generator - This generator generates a view script file that
displays a form to collect input for the specified model class.
• Module Generator - This generator helps you to generate the skel-
eton code needed by a Yii module.
• Extension Generator - This generator helps you to generate the files
needed by a Yii extension.
After choosing a generator by clicking on the “Start” button you will see
a form that allows you to configure the parameters of the generator. Fill
out the form according to your needs and press the “Preview” button to
get a preview of the code that gii is about to generated. Depending on the
generator you chose and whether the files already existed or not, you will
get an output similar to what you see in the following picture:
Clicking on the file name you can view a preview of the code that will be
generated for that file. When the file already exists, gii also provides a diff
view that shows what is different between the code that exists and the one
that will be generated. In this case you can also choose which files should
be overridden and which not.
Tip: When using the Model Generator to update models after
database change, you can copy the code from gii preview and
merge the changes with your own code. You can use IDE features
like PHPStorms compare with clipboard2 for this, which allows
you to merge in relevant changes and leave out others that may
revert your own code.
2
http://www.jetbrains.com/phpstorm/webhelp/comparing-files.html
12.2. THE GII CODE GENERATION TOOL 379
After you have reviewed the code and selected the files to be generated you
can click the “Generate” button to create the files. If all went fine you are
done. When you see errors that gii is not able to generate the files you have
to adjust directory permissions so that your webserver is able to write to the
directories and create the files.
Note: The code generated by gii is only a template that has to be
adjusted to your needs. It is there to help you create new things
quickly but it is not something that creates ready to use code.
We often see people using the models generated by gii without
change and just extend them to adjust some parts of it. This is
not how it is meant to be used. Code generated by gii may be
incomplete or incorrect and has to be changed to fit your needs
before you can use it.
12.2.3 Creating your own templates
Every generator has a form field Code Template that lets you choose a template
to use for code generation. By default gii only provides one template default
but you can create your own templates that are adjusted to your needs.
If you open a folder @appvendoryiisoftyii2-giigenerators, you’ll see six
folders of generators. ‘ + controller - crud
+ default
• extension
• form
• model
• module
This is name generator. If you open any of these folders, you can see
the folder ‘default‘. This folder is name of the template.
Copy folder @appvendoryiisoftyii2-giigeneratorscruddefault to another
location, for example @appmyTemplatescrud. Now open this folder and modify
any template to fit your desires, for example, add errorSummary in views_form
.php:
<?php
//...
<div class="<?= Inflector::camel2id(StringHelper::basename($generator->
modelClass)) ?>-form">
<?= "<?php " ?>$form = ActiveForm::begin(); ?>
<?= "<?=" ?> $form->errorSummary($model) ?> <!-- ADDED HERE -->
<?php foreach ($safeAttributes as $attribute) {
380 CHAPTER 12. DEVELOPMENT TOOLS
echo " <?= " . $generator->generateActiveField($attribute) . "
?>nn";
} ?>
//...
Now you need to tell GII about our template.The setting is made in the
config file:
// config/web.php for basic app
// ...
if (YII_ENV_DEV) {
$config[’modules’][’gii’] = [
’class’ => ’yiigiiModule’,
’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’192.168.178.20’
],
’generators’ => [ //here
’crud’ => [ //name generator
’class’ => ’yiigiigeneratorscrudGenerator’, //class
generator
’templates’ => [ //setting for out templates
’myCrud’ => ’@app/myTemplates/crud/default’, //name
template => path to template
]
]
],
];
}
Open the CRUD generator and you will see that in the field Code Template of
form appeared own template .
12.2.4 Creating your own generators
Open the folder of any generator and you will see two files form.php and
Generator.php. One is the form, the second is the class generator. For create
your own generator, you need to create or override these classes in any folder.
Again as in the previous paragraph customize configuration:
//config/web.php for basic app
//..
if (YII_ENV_DEV) {
$config[’modules’][’gii’] = [
’class’ => ’yiigiiModule’,
’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’192.168.178.20’
],
’generators’ => [
’myCrud’ => [
’class’ => ’appmyTemplatescrudGenerator’,
’templates’ => [
’my’ => ’@app/myTemplates/crud/default’,
]
]
],
];
12.2. THE GII CODE GENERATION TOOL 381
}
// @app/myTemplates/crud/Generator.php
<?php
namespace appmyTemplatescrud;
class Generator extends yiigiiGenerator
{
public function getName()
{
return ’MY CRUD Generator’;
}
public function getDescription()
{
return ’My crud generator. The same as a native, but he is mine...’;
}
// ...
}
Open Gii Module and you will see a new generator appears in it.
382 CHAPTER 12. DEVELOPMENT TOOLS
Error: not existing file: tool-api-doc.md
Chapter 13
Testing
13.1 Testing
Testing is an important part of software development. Whether we are aware
of it or not, we conduct testing continuously. For example, when we write
a class in PHP, we may debug it step by step or simply use echo or die
statements to verify that implementation works according to our initial plan.
In case of web application we’re entering some test data in forms to ensure the
page interacts with us as expected. The testing process could be automated
so that each time when we need to verify something, we just need to call
up the code that do it for us. The code that verifies that result matches
what we’ve planned is called test and the process of its creation and further
execution is known as automated testing, which is the main topic of testing
chapters.
13.1.1 Developing with tests
Test-Driven Development (TDD) and Behavior-Driven Development (BDD)
are approaches of developing software by describing behavior of a piece of
code or the whole feature as a set of scenarios or tests before writing actual
code and only then creating the implementation that allows these tests to
pass verifying that intended behavior is achieved.
The process of developing a feature is the following:
• Create a new test that describes a feature to be implemented.
• Run new test and make sure it fails. It is expected since there’s no
implementation yet.
• Write simple code to make the new test pass.
• Run all tests and make sure they all pass.
• Improve code and make sure tests are still OK.
383
384 CHAPTER 13. TESTING
After it’s done the process is repeated again for another feature or improve-
ment. If existing feature is to be changed, tests should be changed as well.
Tip: If you feel that you are loosing time doing a lot of small
and simple iterations try covering more by your test scenario so
you do more before executing tests again. If you’re debugging
too much try doing the opposite.
The reason to create tests before doing any implemenation is that it allows
you to focus on what do we want to achieve and fully dive into “how to
do it” afterwards. Usually it leads to better abstractions and easier test
maintenance when it comes to feature adjustments in for of less coupled
components.
So to sum up pros of such approach are the following:
• Keeps you focused on one thing at a time so both planning and imple-
mentation are getting better.
• Results in test-covering more features in greater detail i.e. if tests are
OK most probably nothing’s broken.
In the long term it usually gives you a good time-saving effect.
Tip: If you want to know more about the principles for gathering
software requirements and modeling the subject matter it’s good
to learn Domain Driven Development (DDD)1.
13.1.2 When and how to test
While test first approach described above makes sense for long term and
relatively complex projects it could be overkill for simpler ones. There are
some indicators of when it’s appropriate:
• Project is already large and complex.
• Project requirements are starting to get complex. Project grows con-
stantly.
• Project is meant to be long term.
• The cost of the failure is too high.
There’s nothing wrong in creating tests covering behavior of existing imple-
mentation.
• Project is a legacy one to be gradually renewed.
1
https://en.wikipedia.org/wiki/Domain-driven_design
13.2. TESTING ENVIRONMENT SETUP 385
• You’ve got a project to work on and it has no tests.
In some cases any form of automated testing could be overkill:
• Project is simple and isn’t getting any complex.
• It’s one-time project that’s going to be expired.
Still if you have time it’s good to automate testing in these cases as well.
13.1.3 Further reading
• Test Driven Development: By Example / Kent Beck. ISBN: 0321146530.
13.2 Testing environment setup
Note: This section is under development.
Yii2 has officially maintained integration with Codeception2 testing framework
that allows you to create the following test types:
• Unit testing - verifies that a single unit of code is working as expected;
• Functional testing - verifies scenarios from a user’s perspective via
browser emulation;
• Acceptance testing - verifies scenarios from a user’s perspective in a
browser.
Yii provides ready to use test sets for all three test types in both yii2-basic3
and yii2-advanced4 application templates.
In order to run tests you need to install Codeception5. A good way to
install it is the following:
composer global require "codeception/codeception=2.0.*"
composer global require "codeception/specify=*"
composer global require "codeception/verify=*"
If you’ve never used Composer for global packages before, run composer global
status. It should output:
Changed current directory to <directory>
Then add <directory>/vendor/bin to you PATH environment variable. Now
we’re able to use codecept from command line globally.
2
https://github.com/Codeception/Codeception
3
https://github.com/yiisoft/yii2/tree/master/apps/basic
4
https://github.com/yiisoft/yii2/tree/master/apps/advanced
5
https://github.com/Codeception/Codeception
386 CHAPTER 13. TESTING
13.3 Unit Tests
Note: This section is under development.
A unit test verifies that a single unit of code is working as expected. In
object-oriented programming, the most basic code unit is a class. A unit
test thus mainly needs to verify that each of the class interface methods
works properly. That is, given different input parameters, the test verifies
the method returns expected results. Unit tests are usually developed by
people who write the classes being tested.
Unit testing in Yii is built on top of PHPUnit and, optionally, Codecep-
tion so it’s recommended to go through their docs:
• PHPUnit docs starting from chapter 26.
• Codeception Unit Tests7.
13.3.1 Running basic and advanced template unit tests
Please refer to instructions provided in apps/advanced/tests/README.md and apps
/basic/tests/README.md.
13.4 Functional Tests
Note: This section is under development.
• http://codeception.com/docs/05-FunctionalTests
13.4.1 Running basic and advanced template functional tests
Please refer to instructions provided in apps/advanced/tests/README.md and apps
/basic/tests/README.md.
13.5 Acceptance Tests
Note: This section is under development.
• http://codeception.com/docs/04-AcceptanceTests
13.5.1 Running basic and advanced template acceptance tests
Please refer to instructions provided in apps/advanced/tests/README.md and apps
/basic/tests/README.md.
6
http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html
7
http://codeception.com/docs/06-UnitTests
13.6. FIXTURES 387
13.6 Fixtures
Note: This section is under development.
Fixtures are important part of testing. Their main purpose is to set up the
environment in a fixed/known state so that your tests are repeatable and
run in an expected way. Yii provides a fixture framework that allows you to
define your fixtures precisely and use them easily.
A key concept in the Yii fixture framework is the so-called fixture objects.
A fixture object represents a particular aspect of a test environment and is
an instance of yiitestFixture or its child class. For example, you may
use UserFixture to make sure the user DB table contains a fixed set of data.
You load one or multiple fixture objects before running a test and unload
them when finishing.
A fixture may depend on other fixtures, specified via its yiitestFixture
::depends property. When a fixture is being loaded, the fixtures it depends
on will be automatically loaded BEFORE the fixture; and when the fixture is
being unloaded, the dependent fixtures will be unloaded AFTER the fixture.
13.6.1 Defining a Fixture
To define a fixture, create a new class by extending yiitestFixture or
yiitestActiveFixture. The former is best suited for general purpose
fixtures, while the latter has enhanced features specifically designed to work
with database and ActiveRecord.
The following code defines a fixture about the User ActiveRecord and the
corresponding user table.
<?php
namespace apptestsfixtures;
use yiitestActiveFixture;
class UserFixture extends ActiveFixture
{
public $modelClass = ’appmodelsUser’;
}
Tip: Each ActiveFixture is about preparing a DB table for test-
ing purpose. You may specify the table by setting either the yii
testActiveFixture::tableName property or the yiitestActiveFixture
::modelClass property. If the latter, the table name will be
taken from the ActiveRecord class specified by modelClass.
The fixture data for an ActiveFixture fixture is usually provided in a file
located at FixturePath/data/TableName.php, where FixturePath stands for the
directory containing the fixture class file, and TableName is the name of the
388 CHAPTER 13. TESTING
table associated with the fixture. In the example above, the file should be
@app/tests/fixtures/data/user.php. The data file should return an array of
data rows to be inserted into the user table. For example,
<?php
return [
’user1’ => [
’username’ => ’lmayert’,
’email’ => ’strosin.vernice@jerde.com’,
’auth_key’ => ’K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV’,
’password’ => ’$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/
iK0r3jRuwQEs2ldRu.a2’,
],
’user2’ => [
’username’ => ’napoleon69’,
’email’ => ’aileen.barton@heaneyschumm.com’,
’auth_key’ => ’dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q’,
’password’ => ’$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6
viYG5xJExU6’,
],
];
You may give an alias to a row so that later in your test, you may refer to
the row via the alias. In the above example, the two rows are aliased as user1
and user2, respectively.
Also, you do not need to specify the data for auto-incremental columns.
Yii will automatically fill the actual values into the rows when the fixture is
being loaded.
Tip: You may customize the location of the data file by setting
the yiitestActiveFixture::dataFile property. You may
also override yiitestActiveFixture::getData() to provide
the data.
As we described earlier, a fixture may depend on other fixtures. For example,
UserProfileFixture depends on UserFixture because the user profile table con-
tains a foreign key pointing to the user table. The dependency is specified
via the yiitestFixture::depends property, like the following,
namespace apptestsfixtures;
use yiitestActiveFixture;
class UserProfileFixture extends ActiveFixture
{
public $modelClass = ’appmodelsUserProfile’;
public $depends = [’apptestsfixturesUserFixture’];
}
In the above, we have shown how to define a fixture about a DB table.
To define a fixture not related with DB (e.g. a fixture about certain files
13.6. FIXTURES 389
and directories), you may extend from the more general base class yii
testFixture and override the yiitestFixture::load() and yiitest
Fixture::unload() methods.
13.6.2 Using Fixtures
If you are using CodeCeption8 to test your code, you should consider using
the yii2-codeception extension which has the built-in support for loading and
accessing fixtures. If you are using other testing frameworks, you may use
yiitestFixtureTrait in your test cases to achieve the same goal.
In the following we will describe how to write a UserProfile unit test class
using yii2-codeception.
In your unit test class extending yiicodeceptionDbTestCase or yii
codeceptionTestCase, declare which fixtures you want to use in the yii
testFixtureTrait::fixtures() method. For example,
namespace apptestsunitmodels;
use yiicodeceptionDbTestCase;
use apptestsfixturesUserProfileFixture;
class UserProfileTest extends DbTestCase
{
public function fixtures()
{
return [
’profiles’ => UserProfileFixture::className(),
];
}
// ...test methods...
}
The fixtures listed in the fixtures() method will be automatically loaded
before running every test method in the test case and unloaded after fin-
ishing every test method. And as we described before, when a fixture is
being loaded, all its dependent fixtures will be automatically loaded first.
In the above example, because UserProfileFixture depends on UserFixture,
when running any test method in the test class, two fixtures will be loaded
sequentially: UserFixture and UserProfileFixture.
When specifying fixtures in fixtures(), you may use either a class name
or a configuration array to refer to a fixture. The configuration array will let
you customize the fixture properties when the fixture is loaded.
You may also assign an alias to a fixture. In the above example, the
UserProfileFixture is aliased as profiles. In the test methods, you may then
access a fixture object using its alias. For example, $this->profiles will return
the UserProfileFixture object.
8
http://codeception.com/
390 CHAPTER 13. TESTING
Because UserProfileFixture extends from ActiveFixture, you may further
use the following syntax to access the data provided by the fixture:
// returns the data row aliased as ’user1’
$row = $this->profiles[’user1’];
// returns the UserProfile model corresponding to the data row aliased as ’
user1’
$profile = $this->profiles(’user1’);
// traverse every data row in the fixture
foreach ($this->profiles as $row) ...
Info: $this->profiles is still of UserProfileFixture type. The above
access features are implemented through PHP magic methods.
13.6.3 Defining and Using Global Fixtures
The fixtures described above are mainly used by individual test cases. In
most cases, you also need some global fixtures that are applied to ALL or
many test cases. An example is yiitestInitDbFixture which does two
things:
• Perform some common initialization tasks by executing a script located
at @app/tests/fixtures/initdb.php;
• Disable the database integrity check before loading other DB fixtures,
and re-enable it after other DB fixtures are unloaded.
Using global fixtures is similar to using non-global ones. The only differ-
ence is that you declare these fixtures in yiicodeceptionTestCase::
globalFixtures() instead of fixtures(). When a test case loads fixtures, it
will first load global fixtures and then non-global ones.
By default, yiicodeceptionDbTestCase already declares InitDbFixture
in its globalFixtures() method. This means you only need to work with @app
/tests/fixtures/initdb.php if you want to do some initialization work before
each test. You may otherwise simply focus on developing each individual
test case and the corresponding fixtures.
13.6.4 Organizing Fixture Classes and Data Files
By default, fixture classes look for the corresponding data files under the
data folder which is a sub-folder of the folder containing the fixture class
files. You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch different data
files for the same fixture class for different tests. We thus recommend that
you organize the data files in a hierarchical way that is similar to your class
namespaces. For example,
13.6. FIXTURES 391
# under folder testsunitfixtures
data
components
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
In this way you will avoid collision of fixture data files between tests and use
them as you need.
Note: In the example above fixture files are named only for ex-
ample purpose. In real life you should name them according to
which fixture class your fixture classes are extending from. For
example, if you are extending from yiitestActiveFixture for
DB fixtures, you should use DB table names as the fixture data
file names; If you are extending for yiimongodbActiveFixture
for MongoDB fixtures, you should use collection names as the file
names.
The similar hierarchy can be used to organize fixture class files. Instead of
using data as the root directory, you may want to use fixtures as the root
directory to avoid conflict with the data files.
13.6.5 Summary
In the above, we have described how to define and use fixtures. Below we
summarize the typical workflow of running unit tests related with DB:
1. Use yii migrate tool to upgrade your test database to the latest version;
2. Run a test case:
• Load fixtures: clean up the relevant DB tables and populate them
with fixture data;
• Perform the actual test;
• Unload fixtures.
3. Repeat Step 2 until all tests finish.
To be cleaned up below
392 CHAPTER 13. TESTING
13.7 Managing Fixtures
// todo: this tutorial may be merged into test-fixture.md
Fixtures are important part of testing. Their main purpose is to populate
you with data that needed by testing different cases. With this data using
your tests becoming more efficient and useful.
Yii supports fixtures via the yii fixture command line tool. This tool
supports:
• Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
• Unloading fixtures in different ways (usually it is clearing storage);
• Auto-generating fixtures and populating it with random data.
13.7.1 Fixtures format
Fixtures are objects with different methods and configurations, refer to offi-
cial documentation9 on them. Lets assume we have fixtures data to load:
#users.php file under fixtures data path, by default @testsunitfixtures
data
return [
[
’name’ => ’Chase’,
’login’ => ’lmayert’,
’email’ => ’strosin.vernice@jerde.com’,
’auth_key’ => ’K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV’,
’password’ => ’$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/
iK0r3jRuwQEs2ldRu.a2’,
],
[
’name’ => ’Celestine’,
’login’ => ’napoleon69’,
’email’ => ’aileen.barton@heaneyschumm.com’,
’auth_key’ => ’dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q’,
’password’ => ’$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6
viYG5xJExU6’,
],
];
If we are using fixture that loads data into database then these rows will be
applied to users table. If we are using nosql fixtures, for example mongodb
fixture, then this data will be applied to users mongodb collection. In order
to learn about implementing various loading strategies and more, refer to
official documentation10. Above fixture example was auto-generated by yii2
-faker extension, read more about it in these section. Fixture classes name
should not be plural.
9
https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md
10
https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md
13.7. MANAGING FIXTURES 393
13.7.2 Loading fixtures
Fixture classes should be suffixed by Fixture class. By default fixtures will be
searched under testsunitfixtures namespace, you can change this behavior
with config or command options. Note that you can also append fixtures data
to already existing ones with command option --append. You can exclude
some fixtures due load or unload by specifying - before its name like -User.
To load fixture, run the following command:
yii fixture/load <fixture_name>
The required fixture_name parameter specifies a fixture name which data will
be loaded. You can load several fixtures at once. Below are correct formats
of this command:
// load ‘User‘ fixture
yii fixture/load User
// same as above, because default action of "fixture" command is "load"
yii fixture User
// load several fixtures
yii fixture User UserProfile
//load fixture, but don’t clean storage before load and just append to
already existed data
yii fixture User --append
// load all fixtures
yii fixture/load "*"
// same as above
yii fixture "*"
// load all fixtures except ones
yii fixture "*" -DoNotLoadThisOne
// load fixtures, but search them in different namespace. By default
namespace is: testsunitfixtures.
yii fixture User --namespace=’aliasmycustomnamespace’
// load global fixture ‘somenamespaceCustomFixture‘ before other fixtures
will be loaded.
// By default this option is set to ‘InitDbFixture‘ to disable/enable
integrity checks. You can specify several
// global fixtures separated by comma.
yii fixture User --globalFixtures=’somenamespaceCustom’
13.7.3 Unloading fixtures
To unload fixture, run the following command:
394 CHAPTER 13. TESTING
// unload Users fixture, by default it will clear fixture storage (for
example "users" table, or "users" collection if this is mongodb fixture
).
yii fixture/unload User
// Unload several fixtures
yii fixture/unload User,UserProfile
// unload all fixtures
yii fixture/unload "*"
// unload all fixtures except ones
yii fixture/unload "*" -DoNotUnloadThisOne
Same command options like: namespace, globalFixtures also can be applied to
this command.
13.7.4 Configure Command Globally
While command line options allow us to configure the migration command
on-the-fly, sometimes we may want to configure the command once for all.
For example you can configure different migration path as follows:
’controllerMap’ => [
’fixture’ => [
’class’ => ’yiiconsolecontrollersFixtureController’,
’namespace’ => ’myaliassomecustomnamespace’,
’globalFixtures’ => [
’somenamespaceFoo’,
’othernamespaceBar’
],
],
]
13.7.5 Auto-generating fixtures
Yii also can auto-generate fixtures for you based on some template. You
can generate your fixtures with different data on different languages and
formats. These feature is done by Faker11 library and yii2-faker extension.
See extension guide12 for more docs.
11
https://github.com/fzaninotto/Faker
12
https://github.com/yiisoft/yii2/tree/master/extensions/faker
Chapter 14
Special Topics
14.1 Advanced application template
Note: This section is under development.
This template is for large projects developed in teams where the backend
is divided from the frontend, application is deployed to multiple servers
etc. This application template also goes a bit further regarding features
and provides essential database, signup and password restore out of the box.
14.1.1 Installation
Install via Composer
If you do not have Composer1, you may download it from http://getcomposer.org/2
or run the following command on Linux/Unix/MacOS:
curl -sS http://getcomposer.org/installer | php
You can then install the application using the following command:
php composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta2"
php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2-
app-advanced /path/to/yii-application
14.1.2 Getting started
After you install the application, you have to conduct the following steps to
initialize the installed application. You only need to do these once for all.
1. Execute the init command and select dev as environment.
php /path/to/yii-application/init
1
http://getcomposer.org/
2
http://getcomposer.org/
395
396 CHAPTER 14. SPECIAL TOPICS
Otherwise, in production execute init in non-interactive mode.
php /path/to/yii-application/init --env=Production overwrite=All
2. Create a new database and adjust the components.db configuration in
common/config/main-local.php accordingly.
3. Apply migrations with console command yii migrate.
4. Set document roots of your web server:
• for frontend /path/to/yii-application/frontend/web/ and using the URL
http://frontend/
• for backend /path/to/yii-application/backend/web/ and using the URL
http://backend/
14.1.3 Directory structure
The root directory contains the following subdirectories:
• backend - backend web application.
• common - files common to all applications.
• console - console application.
• environments - environment configs.
• frontend - frontend web application.
Root directory contains a set of files.
• .gitignore contains a list of directories ignored by git version system.
If you need something never get to your source code repository, add it
there.
• composer.json - Composer config described in detail below.
• init - initialization script described in “Composer config described in
detail below”.
• init.bat - same for Windows.
• LICENSE.md - license info. Put your project license there. Especially
when opensourcing.
• README.md - basic info about installing template. Consider replacing it
with information about your project and its installation.
14.1. ADVANCED APPLICATION TEMPLATE 397
• requirements.php - Yii requirements checker.
• yii - console application bootstrap.
• yii.bat - same for Windows.
14.1.4 Predefined path aliases
• @yii - framework directory.
• @app - base path of currently running application.
• @common - common directory.
• @frontend - frontend web application directory.
• @backend - backend web application directory.
• @console - console directory.
• @runtime - runtime directory of currently running web application.
• @vendor - Composer vendor directory.
• @web - base URL of currently running web application.
• @webroot - web root directory of currently running web application.
The aliases specific to the directory structure of the advanced application
(@common, @frontend, @backend, and @console) are defined in common/config/bootstrap
.php.
14.1.5 Applications
There are three applications in advanced template: frontend, backend and
console. Frontend is typically what is presented to end user, the project
itself. Backend is admin panel, analytics and such functionality. Console is
typically used for cron jobs and low-level server management. Also it’s used
during application deployment and handles migrations and assets.
There’s also a common directory that contains files used by more than one
application. For example, User model.
frontend and backend are both web applications and both contain the
web directory. That’s the webroot you should point your web server to.
Each application has its own namespace and alias corresponding to its
name. Same applies to common directory.
398 CHAPTER 14. SPECIAL TOPICS
14.1.6 Configuration and environments
There are multiple problems with a typical approach to configuration:
• Each team member has its own configuration options. Committing
such config will affect other team members.
• Production database password and API keys should not end up in the
repository.
• There are multiple server environments: development, testing, produc-
tion. Each should have its own configuration.
• Defining all configuration options for each case is very repetitive and
takes too much time to maintain.
In order to solve these issues Yii introduces a simple environments concept.
Each environment is represented by a set of files under the environments
directory. The init command is used to switch between these. What it
really does is copy everything from the environment directory over to the
root directory where all applications are.
Typically environment contains application bootstrap files such as index
.php and config files suffixed with -local.php. These are added to .gitignore
and never added to source code repository.
In order to avoid duplication configurations are overriding each other.
For example, the frontend reads configuration in the following order:
• common/config/main.php
• common/config/main-local.php
• frontend/config/main.php
• frontend/config/main-local.php
Parameters are read in the following order:
• common/config/params.php
• common/config/params-local.php
• frontend/config/params.php
• frontend/config/params-local.php
The later config file overrides the former.
Here’s the full scheme:
14.1. ADVANCED APPLICATION TEMPLATE 399
14.1.7 Configuring Composer
After the application template is installed it’s a good idea to adjust default
composer.json that can be found in the root directory:
{
"name": "yiisoft/yii2-app-advanced",
"description": "Yii 2 Advanced Application Template",
"keywords": ["yii", "framework", "advanced", "application template"],
"homepage": "http://www.yiiframework.com/",
"type": "project",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"forum": "http://www.yiiframework.com/forum/",
400 CHAPTER 14. SPECIAL TOPICS
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"minimum-stability": "dev",
"require": {
"php": ">=5.4.0",
"yiisoft/yii2": "*",
"yiisoft/yii2-swiftmailer": "*",
"yiisoft/yii2-bootstrap": "*",
"yiisoft/yii2-debug": "*",
"yiisoft/yii2-gii": "*"
},
"scripts": {
"post-create-project-cmd": [
"yiicomposerInstaller::setPermission"
]
},
"extra": {
"writable": [
"backend/runtime",
"backend/web/assets",
"console/runtime",
"console/migrations",
"frontend/runtime",
"frontend/web/assets"
]
}
}
First we’re updating basic information. Change name, description, keywords,
homepage and support to match your project.
Now the interesting part. You can add more packages your applica-
tion needs to the require section. All these packages are coming from pack-
agist.org3 so feel free to browse the website for useful code.
After your composer.json is changed you can run php composer.phar update
--prefer-dist, wait till packages are downloaded and installed and then just
use them. Autoloading of classes will be handled automatically.
14.1.8 Creating links from backend to frontend
Often it’s required to create links from the backend application to the fron-
tend application. Since the frontend application may contain its own URL
manager rules you need to duplicate that for the backend application by
naming it differently:
return [
’components’ => [
3
https://packagist.org/
14.2. CREATING YOUR OWN APPLICATION STRUCTURE 401
’urlManager’ => [
// here is your normal backend url manager config
],
’urlManagerFrontend’ => [
// here is your frontend URL manager config
],
],
];
After it is done, you can get an URL pointing to frontend like the following:
echo Yii::$app->urlManagerFrontend->createUrl(...);
14.2 Creating your own Application structure
Note: This section is under development.
While the basic and advanced application templates are great for most of
your needs, you may want to create your own application template with
which to start your projects.
Application templates in Yii are simply repositories containing a composer
.json file, and registered as a Composer package. Any repository can be
identified as a Composer package, making it installable via create-project
Composer command.
Since it’s a bit too much to start building your entire template from
scratch, it is better to use one of the built-in templates as a base. Let’s use
the basic template here.
14.2.1 Clone the Basic Template
The first step is to clone the basic Yii template’s Git repository:
git clone git@github.com:yiisoft/yii2-app-basic.git
Then wait for the repository to be downloaded to your computer. Since the
changes made to the template won’t be pushed back, you can delete the .git
diretory and all of its contents from the download.
14.2.2 Modify the Files
Next, you’ll want to modify the composer.json to reflect your template. Change
the name, description, keywords, homepage, license, and support values to de-
scribe your new template. Also adjust the require, require-dev, suggest, and
other options to match your template’s requirements.
Note: In the composer.json file, use the writable parameter under
extra to specify per file permissions to be set after an application
is created using the template.
402 CHAPTER 14. SPECIAL TOPICS
Next, actually modify the structure and contents of the application as you
would like the default to be. Finally, update the README file to be applic-
able to your template.
14.2.3 Make a Package
With the template defined, create a Git repository from it, and push your
files there. If you’re going to open source your template, Github4 is the best
place to host it. If you intend to keep your template non-collaborative, any
Git repository site will do.
Next, you need to register your package for Composer’s sake. For public
templates, the package should be registered at Packagist5. For private tem-
plates, it is a bit more tricky to register the packge. For instructions, see the
Composer documentation6.
14.2.4 Use the Template
That’s all that’s required to create a new Yii application template. Now you
can create projects using your template:
php composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta2"
php composer.phar create-project --prefer-dist --stability=dev mysoft/yii2-
app-coolone new-project
14.3 Console applications
Note: This section is under development.
Yii has full featured support for console applications, whose structure is
very similar to a Yii web application. A console application consists of one
or more yiiconsoleController classes, which are often referred to as
“commands” in the console environment. Each controller can also have one
or more actions, just like web controllers.
14.3.1 Usage
You execute a console controller action using the following syntax:
yii <route> [--option1=value1 --option2=value2 ... argument1 argument2 ...]
For example, the yiiconsolecontrollersMigrateController::actionCreate()
with yiiconsolecontrollersMigrateController::$migrationTable set
can be called from command line like so:
4
http://githumb.com
5
https://packagist.org/
6
https://getcomposer.org/doc/05-repositories.md#hosting-your-own
14.3. CONSOLE APPLICATIONS 403
yii migrate/create --migrationTable=my_migration
In the above yii is the console application entry script described below.
14.3.2 Entry script
The console application entry script is equivalent to the index.php bootstrap
file used for the web application. The console entry script is typically called
yii, and located in your application’s root directory. The contents of the
console application entry script contains code like the following:
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap file.
*/
defined(’YII_DEBUG’) or define(’YII_DEBUG’, true);
// fcgi doesn’t have STDIN and STDOUT defined by default
defined(’STDIN’) or define(’STDIN’, fopen(’php://stdin’, ’r’));
defined(’STDOUT’) or define(’STDOUT’, fopen(’php://stdout’, ’w’));
require(__DIR__ . ’/vendor/autoload.php’);
require(__DIR__ . ’/vendor/yiisoft/yii2/Yii.php’);
$config = require(__DIR__ . ’/config/console.php’);
$application = new yiiconsoleApplication($config);
$exitCode = $application->run();
exit($exitCode);
This script will be created as part of your application; you’re free to edit
it to suit your needs. The YII_DEBUG constant can be set false if you do
not want to see a stack trace on error, and/or if you want to improve the
overall performance. In both basic and advanced application templates, the
console application entry script has debugging enabled to provide a more
developer-friendly environment.
14.3.3 Configuration
As can be seen in the code above, the console application uses its own con-
figuration file, named console.php. In this file you should configure various
application components and properties for the console application in partic-
ular.
If your web application and the console application share a lot of config-
uration parameters and values, you may consider moving the common parts
into a separate file, and including this file in both of the application configur-
ations (web and console). You can see an example of this in the “advanced”
application template.
404 CHAPTER 14. SPECIAL TOPICS
Sometimes, you may want to run a console command using an application
configuration that is different from the one specified in the entry script. For
example, you may want to use the yii migrate command to upgrade your
test databases, which are configured in each individual test suite. To do
change the configuration dynamically, simply specify a custom application
configuration file via the appconfig option when executing the command:
yii <route> --appconfig=path/to/config.php ...
Note: When using * in console don’t forget to quote it as "*" in
order to avoid executing it as a shell command.
14.3.4 Creating your own console commands
Console Controller and Action
A console command is defined as a controller class extending from yii
consoleController. In the controller class, you define one or more actions
that correspond to sub-commands of the controller. Within each action, you
write code that implements the appropriate tasks for that particular sub-
command.
When running a command, you need to specify the route to the controller
action. For example, the route migrate/create invokes the sub-command
that corresponds to the yiiconsolecontrollersMigrateController::
actionCreate() action method. If a route offered during execution does
not contain an action ID, the default action will be executed (as with a web
controller).
Options
By overriding the yiiconsoleController::options() method, you can
specify options that are available to a console command (controller/actionID).
The method should return a list of the controller class’s public properties.
When running a command, you may specify the value of an option using the
syntax --OptionName=OptionValue. This will assign OptionValue to the OptionName
property of the controller class.
If the default value of an option is of an array type and you set this
option while running the command, the option value will be converted into
an array by splitting the input string on any commas.
Arguments
Besides options, a command can also receive arguments. The arguments
will be passed as the parameters to the action method corresponding to
the requested sub-command. The first argument corresponds to the first
parameter, the second corresponds to the second, and so on. If not enough
14.3. CONSOLE APPLICATIONS 405
arguments are provided when the command is called, the corresponding para-
meters will take the declared default values, if defined. If no default value
is set, and no value is provided at runtime, the command will exit with an
error.
You may use the array type hint to indicate that an argument should be
treated as an array. The array will be generated by splitting the input string
on commas.
The follow examples show how to declare arguments:
class ExampleController extends yiiconsoleController
{
// The command "yii example/create test" will call "actionCreate(’test’)
"
public function actionCreate($name) { ... }
// The command "yii example/index city" will call "actionIndex(’city’, ’
name’)"
// The command "yii example/index city id" will call "actionIndex(’city
’, ’id’)"
public function actionIndex($category, $order = ’name’) { ... }
// The command "yii example/add test" will call "actionAdd([’test’])"
// The command "yii example/add test1,test2" will call "actionAdd([’
test1’, ’test2’])"
public function actionAdd(array $name) { ... }
}
Exit Code
Using exit codes is a best practice for console application development. Con-
ventionally, a command returns 0 to indicate that everything is OK. If the
command returns a number greater than zero, that’s considered to be indic-
ative of an error. The number returned will be the error code, potentially
usable to find out details about the error. For example 1 could stand gener-
ally for an unknown error and all codes above would be reserved for specific
cases: input errors, missing files, and so forth.
To have your console command return an exit code, simply return an
integer in the controller action method:
public function actionIndex()
{
if (/* some problem */) {
echo "A problem occured!n";
return 1;
}
// do something
return 0;
}
There are some predefined constants you can use:
406 CHAPTER 14. SPECIAL TOPICS
• Controller::EXIT_CODE_NORMAL with value of 0;
• Controller::EXIT_CODE_ERROR with value of 1.
14.4 Core Validators
Yii provides a set of commonly used core validators, found primarily un-
der the yiivalidators namespace. Instead of using lengthy validator class
names, you may use aliases to specify the use of these core validators. For
example, you can use the alias required to refer to the yiivalidators
RequiredValidator class:
public function rules()
{
return [
[[’email’, ’password’], ’required’],
];
}
The yiivalidatorsValidator::builtInValidators property declares all
supported validator aliases.
In the following, we will describe the main usage and properties of every
core validator.
14.4.1 yiivalidatorsBooleanValidator
[
// checks if "selected" is either 0 or 1, regardless of data type
[’selected’, ’boolean’],
// checks if "deleted" is of boolean type, either true or false
[’deleted’, ’boolean’, ’trueValue’ => true, ’falseValue’ => false, ’
strict’ => true],
]
This validator checks if the input value is a boolean.
• trueValue: the value representing true. Defaults to ’1’.
• falseValue: the value representing false. Defaults to ’0’.
• strict: whether the type of the input value should match that of
trueValue and falseValue. Defaults to false.
Note: Because data input submitted via HTML forms are all
strings, you normally should leave the yiivalidatorsBooleanValidator
::strict property as false.
14.4. CORE VALIDATORS 407
14.4.2 yiicaptchaCaptchaValidator
[
[’verificationCode’, ’captcha’],
]
This validator is usually used together with yiicaptchaCaptchaAction
and yiicaptchaCaptcha to make sure an input is the same as the verific-
ation code displayed by yiicaptchaCaptcha widget.
• caseSensitive: whether the comparison of the verification code is case
sensitive. Defaults to false.
• captchaAction: the route corresponding to the yiicaptchaCaptchaAction
that renders the CAPTCHA image. Defaults to ’site/captcha’.
• skipOnEmpty: whether the validation can be skipped if the input is
empty. Defaults to false, which means the input is required.
14.4.3 yiivalidatorsCompareValidator
[
// validates if the value of "password" attribute equals to that of "
password_repeat"
[’password’, ’compare’],
// validates if age is greater than or equal to 30
[’age’, ’compare’, ’compareValue’ => 30, ’operator’ => ’>=’],
]
This validator compares the specified input value with another one and make
sure if their relationship is as specified by the operator property.
• compareAttribute: the name of the attribute whose value should be com-
pared with. When the validator is being used to validate an attribute,
the default value of this property would be the name of the attribute
suffixed with _repeat. For example, if the attribute being validated is
password, then this property will default to password_repeat.
• compareValue: a constant value that the input value should be compared
with. When both of this property and compareAttribute are specified,
this property will take precedence.
• operator: the comparison operator. Defaults to ==, meaning checking
if the input value is equal to that of compareAttribute or compareValue.
The following operators are supported:
– ==: check if two values are equal. The comparison is done is non-
strict mode.
– ===: check if two values are equal. The comparison is done is strict
mode.
408 CHAPTER 14. SPECIAL TOPICS
– !=: check if two values are NOT equal. The comparison is done
is non-strict mode.
– !==: check if two values are NOT equal. The comparison is done
is strict mode.
– >: check if value being validated is greater than the value being
compared with.
– >=: check if value being validated is greater than or equal to the
value being compared with.
– <: check if value being validated is less than the value being com-
pared with.
– <=: check if value being validated is less than or equal to the value
being compared with.
14.4.4 yiivalidatorsDateValidator
[
[[’from’, ’to’], ’date’],
]
This validator checks if the input value is a date, time or datetime in a proper
format. Optionally, it can convert the input value into a UNIX timestamp
and store it in an attribute specified via yiivalidatorsDateValidator::
timestampAttribute.
• format: the date/time format that the value being validated should be
in. This can be a date time pattern as described in the ICU manual7.
Alternatively this can be a string prefixed with php: representing a
format that can be recognized by the PHP Datetime class. Please refer
to http://php.net/manual/en/datetime.createfromformat.php on
supported formats. If this is not set, it will take the value of Yii::$app
->formatter->dateFormat.
• timestampAttribute: the name of the attribute to which this validator
may assign the UNIX timestamp converted from the input date/time.
14.4.5 yiivalidatorsDefaultValueValidator
[
// set "age" to be null if it is empty
[’age’, ’default’, ’value’ => null],
// set "country" to be "USA" if it is empty
[’country’, ’default’, ’value’ => ’USA’],
7
http://userguide.icu-project.org/formatparse/datetime#
TOC-Date-Time-Format-Syntax
14.4. CORE VALIDATORS 409
// assign "from" and "to" with a date 3 days and 6 days from today, if
they are empty
[[’from’, ’to’], ’default’, ’value’ => function ($model, $attribute) {
return date(’Y-m-d’, strtotime($attribute === ’to’ ? ’+3 days’ : ’+6
days’));
}],
]
This validator does not validate data. Instead, it assigns a default value to
the attributes being validated if the attributes are empty.
• value: the default value or a PHP callable that returns the default
value which will be assigned to the attributes being validated if they
are empty. The signature of the PHP callable should be as follows,
function foo($model, $attribute) {
// ... compute $value ...
return $value;
}
Info: How to determine if a value is empty or not is a separate
topic covered in the Empty Values section.
14.4.6 yiivalidatorsNumberValidator
[
// checks if "salary" is a double number
[’salary’, ’double’],
]
This validator checks if the input value is a double number. It is equivalent
to the number validator.
• max: the upper limit (inclusive) of the value. If not set, it means the
validator does not check the upper limit.
• min: the lower limit (inclusive) of the value. If not set, it means the
validator does not check the lower limit.
14.4.7 yiivalidatorsEmailValidator
[
// checks if "email" is a valid email address
[’email’, ’email’],
]
This validator checks if the input value is a valid email address.
• allowName: whether to allow name in the email address (e.g. John Smith
<john.smith@example.com>). Defaults to false.
410 CHAPTER 14. SPECIAL TOPICS
• checkDNS, whether to check whether the email’s domain exists and has
either an A or MX record. Be aware that this check may fail due to
temporary DNS problems, even if the email address is actually valid.
Defaults to false.
• enableIDN, whether the validation process should take into account IDN
(internationalized domain names). Defaults to false. Note that in order
to use IDN validation you have to install and enable the intl PHP
extension, or an exception would be thrown.
14.4.8 yiivalidatorsExistValidator
[
// a1 needs to exist in the column represented by the "a1" attribute
[’a1’, ’exist’],
// a1 needs to exist, but its value will use a2 to check for the
existence
[’a1’, ’exist’, ’targetAttribute’ => ’a2’],
// a1 and a2 need to exist together, and they both will receive error
message
[[’a1’, ’a2’], ’exist’, ’targetAttribute’ => [’a1’, ’a2’]],
// a1 and a2 need to exist together, only a1 will receive error message
[’a1’, ’exist’, ’targetAttribute’ => [’a1’, ’a2’]],
// a1 needs to exist by checking the existence of both a2 and a3 (using
a1 value)
[’a1’, ’exist’, ’targetAttribute’ => [’a2’, ’a1’ => ’a3’]],
// a1 needs to exist. If a1 is an array, then every element of it must
exist.
[’a1’, ’exist’, ’allowArray’ => true],
]
This validator checks if the input value can be found in a table column.
It only works with Active Record model attributes. It supports validation
against either a single column or multiple columns.
• targetClass: the name of the Active Record class that should be used
to look for the input value being validated. If not set, the class of the
model currently being validated will be used.
• targetAttribute: the name of the attribute in targetClass that should
be used to validate the existence of the input value. If not set, it will
use the name of the attribute currently being validated. You may use
an array to validate the existence of multiple columns at the same
time. The array values are the attributes that will be used to validate
the existence, while the array keys are the attributes whose values are
14.4. CORE VALIDATORS 411
to be validated. If the key and the value are the same, you can just
specify the value.
• filter: additional filter to be applied to the DB query used to check
the existence of the input value. This can be a string or an array
representing the additional query condition (refer to yiidbQuery::
where() on the format of query condition), or an anonymous function
with the signature function ($query), where $query is the yiidbQuery
object that you can modify in the function.
• allowArray: whether to allow the input value to be an array. Defaults to
false. If this property is true and the input is an array, then every ele-
ment of the array must exist in the target column. Note that this prop-
erty cannot be set true if you are validating against multiple columns
by setting targetAttribute as an array.
14.4.9 yiivalidatorsFileValidator
[
// checks if "primaryImage" is an uploaded image file in PNG, JPG or GIF
format.
// the file size must be less than 1MB
[’primaryImage’, ’file’, ’extensions’ => [’png’, ’jpg’, ’gif’], ’maxSize
’ => 1024*1024*1024],
]
This validator checks if the input is a valid uploaded file.
• extensions: a list of file name extensions that are allowed to be up-
loaded. This can be either an array or a string consisting of file exten-
sion names separated by space or comma (e.g. “gif, jpg”). Extension
names are case-insensitive. Defaults to null, meaning all file name
extensions are allowed.
• mimeTypes: a list of file MIME types that are allowed to be uploaded.
This can be either an array or a string consisting of file MIME types
separated by space or comma (e.g. “image/jpeg, image/png”). Mime
type names are case-insensitive. Defaults to null, meaning all MIME
types are allowed.
• minSize: the minimum number of bytes required for the uploaded file.
Defaults to null, meaning no lower limit.
• maxSize: the maximum number of bytes allowed for the uploaded file.
Defaults to null, meaning no upper limit.
• maxFiles: the maximum number of files that the given attribute can
hold. Defaults to 1, meaning the input must be a single uploaded file.
412 CHAPTER 14. SPECIAL TOPICS
If it is greater than 1, then the input must be an array consisting of at
most maxFiles number of uploaded files.
• checkExtensionByMimeType: whether to check the file extension by the
file’s MIME type. If the extension produced by MIME type check
differs from the uploaded file extension, the file will be considered as
invalid. Defaults to true, meaning perform such check.
FileValidator is used together with yiiwebUploadedFile. Please refer to
the Uploading Files section for complete coverage about uploading files and
performing validation about the uploaded files.
14.4.10 yiivalidatorsFilterValidator
[
// trim "username" and "email" inputs
[[’username’, ’email’], ’filter’, ’filter’ => ’trim’, ’skipOnArray’ =>
true],
// normalize "phone" input
[’phone’, ’filter’, ’filter’ => function ($value) {
// normalize phone input here
return $value;
}],
]
This validator does not validate data. Instead, it applies a filter on the input
value and assigns it back to the attribute being validated.
• filter: a PHP callback that defines a filter. This can be a global
function name, an anonymous function, etc. The function signature
must be function ($value) { return $newValue; }. This property must
be set.
• skipOnArray: whether to skip the filter if the input value is an array.
Defaults to false. Note that if the filter cannot handle array input, you
should set this property to be true. Otherwise some PHP error might
occur.
Tip: If you want to trim input values, you may directly use trim
validator.
14.4.11 yiivalidatorsImageValidator
[
// checks if "primaryImage" is a valid image with proper size
[’primaryImage’, ’image’, ’extensions’ => ’png, jpg’,
’minWidth’ => 100, ’maxWidth’ => 1000,
’minHeight’ => 100, ’maxHeight’ => 1000,
],
]
14.4. CORE VALIDATORS 413
This validator checks if the input value represents a valid image file. It
extends from the file validator and thus inherits all its properties. Besides,
it supports the following additional properties specific for image validation
purpose:
• minWidth: the minimum width of the image. Defaults to null, meaning
no lower limit.
• maxWidth: the maximum width of the image. Defaults to null, meaning
no upper limit.
• minHeight: the minimum height of the image. Defaults to null, meaning
no lower limit.
• maxHeight: the maximum height of the image. Defaults to null, meaning
no upper limit.
14.4.12 yiivalidatorsRangeValidator
[
// checks if "level" is 1, 2 or 3
[’level’, ’in’, ’range’ => [1, 2, 3]],
]
This validator checks if the input value can be found among the given list of
values.
• range: a list of given values within which the input value should be
looked for.
• strict: whether the comparison between the input value and the given
values should be strict (both the type and value must be the same).
Defaults to false.
• not: whether the validation result should be inverted. Defaults to false.
When this property is set true, the validator checks if the input value
is NOT among the given list of values.
• allowArray: whether to allow the input value to be an array. When this
is true and the input value is an array, every element in the array must
be found in the given list of values, or the validation would fail.
14.4.13 yiivalidatorsNumberValidator
[
// checks if "age" is an integer
[’age’, ’integer’],
]
414 CHAPTER 14. SPECIAL TOPICS
This validator checks if the input value is an integer.
• max: the upper limit (inclusive) of the value. If not set, it means the
validator does not check the upper limit.
• min: the lower limit (inclusive) of the value. If not set, it means the
validator does not check the lower limit.
14.4.14 yiivalidatorsRegularExpressionValidator
[
// checks if "username" starts with a letter and contains only word
characters
[’username’, ’match’, ’pattern’ => ’/^[a-z]w*$/i’]
]
This validator checks if the input value matches the specified regular expres-
sion.
• pattern: the regular expression that the input value should match. This
property must be set, or an exception will be thrown.
• not: whether to invert the validation result. Defaults to false, meaning
the validation succeeds only if the input value matches the pattern. If
this is set true, the validation is considered successful only if the input
value does NOT match the pattern.
14.4.15 yiivalidatorsNumberValidator
[
// checks if "salary" is a number
[’salary’, ’number’],
]
This validator checks if the input value is a number. It is equivalent to the
double validator.
• max: the upper limit (inclusive) of the value. If not set, it means the
validator does not check the upper limit.
• min: the lower limit (inclusive) of the value. If not set, it means the
validator does not check the lower limit.
14.4.16 yiivalidatorsRequiredValidator
[
// checks if both "username" and "password" are not empty
[[’username’, ’password’], ’required’],
]
14.4. CORE VALIDATORS 415
This validator checks if the input value is provided and not empty.
• requiredValue: the desired value that the input should be. If not set, it
means the input should not be empty.
• strict: whether to check data types when validating a value. Defaults
to false. When requiredValue is not set, if this property is true, the
validator will check if the input value is not strictly null; If this property
is false, the validator will use a loose rule to determine a value is empty
or not. When requiredValue is set, the comparison between the input
and requiredValue will also check data types if this property is true.
Info: How to determine if a value is empty or not is a separate
topic covered in the Empty Values section.
14.4.17 yiivalidatorsSafeValidator
[
// marks "description" to be a safe attribute
[’description’, ’safe’],
]
This validator does not perform data validation. Instead, it is used to mark
an attribute to be a safe attribute.
14.4.18 yiivalidatorsStringValidator
[
// checks if "username" is a string whose length is between 4 and 24
[’username’, ’string’, ’length’ => [4, 24]],
]
This validator checks if the input value is a valid string with certain length.
• length: specifies the length limit of the input string being validated.
This can be specified in one of the following forms:
– an integer: the exact length that the string should be of;
– an array of one element: the minimum length of the input string
(e.g. [8]). This will overwrite min.
– an array of two elements: the minimum and maximum lengths of
the input string (e.g. [8, 128]). This will overwrite both min and
max.
• min: the minimum length of the input string. If not set, it means no
minimum length limit.
• max: the maximum length of the input string. If not set, it means no
maximum length limit.
416 CHAPTER 14. SPECIAL TOPICS
• encoding: the encoding of the input string to be validated. If not set,
it will use the application’s yiibaseApplication::charset value
which defaults to UTF-8.
14.4.19 yiivalidatorsFilterValidator
[
// trims the white spaces surrounding "username" and "email"
[[’username’, ’email’], ’trim’],
]
This validator does not perform data validation. Instead, it will trim the
surrounding white spaces around the input value. Note that if the input
value is an array, it will be ignored by this validator.
14.4.20 yiivalidatorsUniqueValidator
[
// a1 needs to be unique in the column represented by the "a1" attribute
[’a1’, ’unique’],
// a1 needs to be unique, but column a2 will be used to check the
uniqueness of the a1 value
[’a1’, ’unique’, ’targetAttribute’ => ’a2’],
// a1 and a2 need to be unique together, and they both will receive
error message
[[’a1’, ’a2’], ’unique’, ’targetAttribute’ => [’a1’, ’a2’]],
// a1 and a2 need to be unique together, only a1 will receive error
message
[’a1’, ’unique’, ’targetAttribute’ => [’a1’, ’a2’]],
// a1 needs to be unique by checking the uniqueness of both a2 and a3 (
using a1 value)
[’a1’, ’unique’, ’targetAttribute’ => [’a2’, ’a1’ => ’a3’]],
]
This validator checks if the input value is unique in a table column. It only
works with Active Record model attributes. It supports validation against
either a single column or multiple columns.
• targetClass: the name of the Active Record class that should be used
to look for the input value being validated. If not set, the class of the
model currently being validated will be used.
• targetAttribute: the name of the attribute in targetClass that should
be used to validate the uniqueness of the input value. If not set, it will
use the name of the attribute currently being validated. You may use
an array to validate the uniqueness of multiple columns at the same
time. The array values are the attributes that will be used to validate
the uniqueness, while the array keys are the attributes whose values
14.5. INTERNATIONALIZATION 417
are to be validated. If the key and the value are the same, you can just
specify the value.
• filter: additional filter to be applied to the DB query used to check
the uniqueness of the input value. This can be a string or an array
representing the additional query condition (refer to yiidbQuery::
where() on the format of query condition), or an anonymous function
with the signature function ($query), where $query is the yiidbQuery
object that you can modify in the function.
14.4.21 yiivalidatorsUrlValidator
[
// checks if "website" is a valid URL. Prepend "http://" to the "website
" attribute
// if it does not have a URI scheme
[’website’, ’url’, ’defaultScheme’ => ’http’],
]
This validator checks if the input value is a valid URL.
• validSchemes: an array specifying the URI schemes that should be con-
sidered valid. Defaults to [’http’, ’https’], meaning both http and
https URLs are considered to be valid.
• defaultScheme: the default URI scheme to be prepended to the input
if it does not have the scheme part. Defaults to null, meaning do not
modify the input value.
• enableIDN: whether the validator should take into account IDN (inter-
nationalized domain names). Defaults to false. Note that in order
to use IDN validation you have to install and enable the intl PHP
extension, otherwise an exception would be thrown.
14.5 Internationalization
Note: This section is under development.
Internationalization (I18N) refers to the process of designing a software ap-
plication so that it can be adapted to various languages and regions without
engineering changes. For Web applications, this is of particular importance
because the potential users may be worldwide.
Yii offers several tools that help with internationalisation of a website
such as [message translation][], [number and date formatting][].
418 CHAPTER 14. SPECIAL TOPICS
14.5.1 Locale and Language
There are two languages defined in the Yii application: yiibaseApplication
::$sourceLanguage and yiibaseApplication::$language.
Source language is the language original application messages are written
in directly in the code such as:
echo Yii::t(’app’, ’I am a message!’);
The target language is the language that should be used to display the cur-
rent page i.e. the language that original messages need to be translated to.
It is defined in the application configuration like the following:
return [
’id’ => ’applicationID’,
’basePath’ => dirname(__DIR__),
// ...
’language’ => ’ru-RU’, // <- here!
// ...
]
Tip: The default value for the yiibaseApplication::$sourceLanguage
is English and it is recommended to keep this value. The reason
is that it’s easier to find people translating from English to any
language than from non-English to non-English.
You may set the application language at runtime to set it to a language
the user has chosen. This has to be done at a point before any output is
generated so that it affects all the output correctly. Therefor just change the
application property to the desired value:
Yii::$app->language = ’zh-CN’;
The format for the language/locale is ll-CC where ll is a two- or three-letter
lowercase code for a language according to ISO-6398 and CC is the country
code according to ISO-31669.
Note: For more information on the concept and syntax of locales,
check the documentation of the ICU project10.
14.5.2 Message translation
Message translation is used to translate messages that are ouput by an ap-
plicaiton to different languages so that users from different countries can use
the application in their native language.
8
http://www.loc.gov/standards/iso639-2/
9
http://www.iso.org/iso/en/prods-services/iso3166ma/
02iso-3166-code-lists/list-en1.html
10
http://userguide.icu-project.org/locale#TOC-The-Locale-Concept
14.5. INTERNATIONALIZATION 419
The message translation feature in Yii works simply as finding a trans-
lation of the message from a source language into a target language. To use
the message translation feature you wrap your original message strings with
a call to the Yii::t() method. The first parameter of this method takes a
category which helps to distingish the source of messages in differnet parts
of the application and the second parameter is the message itself.
echo Yii::t(’app’, ’This is a string to translate!’);
Yii tries to load an appropriate translation according to the current yii
baseApplication::$language from one of the message sources defined
in the i18n application component. A message source is a set of files or a
database that provides translation messages. The following configuration
example defines a messages source that takes the messages from PHP files:
’components’ => [
// ...
’i18n’ => [
’translations’ => [
’app*’ => [
’class’ => ’yiii18nPhpMessageSource’,
//’basePath’ => ’@app/messages’,
//’sourceLanguage’ => ’en-US’,
’fileMap’ => [
’app’ => ’app.php’,
’app/error’ => ’error.php’,
],
],
],
],
],
In the above app* is a pattern that specifies which categories are handled
by the message source. In this case we’re handling everything that begins
with app. Message files are located in @app/messages, the messages directory in
your application directory. The yiii18nPhpMessageSource::fileMap ar-
ray defines which file is to be used for which category. Instead of configuring
fileMap you can rely on convention which is to use the category name as the
file name (e.g. category app/error will result in the file name app/error.php
under the yiii18nPhpMessageSource::basePath.
When translating the message for Yii::t(’app’, ’This is a string to
translate!’) and an application language ru-RU, Yii will first look for a file
@app/messages/ru-RU/app.php to retrieve the list of available translations. If
there is file ru-RU it will try ru as well before failing.
Beside storing messages in PHP files (using yiii18nPhpMessageSource)
Yii provides two other classes:
• yiii18nGettextMessageSource that uses GNU Gettext MO or PO
files.
420 CHAPTER 14. SPECIAL TOPICS
• yiii18nDbMessageSource that uses a database.
Named placeholders
You can add parameters to a translation message that will be substituted
with the corresponding value after translation. The format for this is to use
curly brackets around the parameter name as you can see in the following
example:
$username = ’Alexander’;
echo Yii::t(’app’, ’Hello, {username}!’, [
’username’ => $username,
]);
Note that the parameter assignment is without the brackets.
Positional placeholders
$sum = 42;
echo Yii::t(’app’, ’Balance: {0}’, $sum);
Tip: Try keep message strings meaningful and avoid using too
many positional parameters. Remember that translator has source
string only so it should be obvious about what will replace each
placeholder.
Advanced placeholder formatting
In order to use advanced features you need to install and enable the intl PHP
extension11. After installing and enabling it you will be able to use extended
syntax for placeholders. Either short form {placeholderName, argumentType
} that means default setting or full form {placeholderName, argumentType,
argumentStyle} that allows you to specify formatting style.
A complete reference is available at the ICU website12 but we will show
some examples in the following.
$sum = 42;
echo Yii::t(’app’, ’Balance: {0, number}’, $sum);
You can specify one of the built-in styles (integer, currency, percent):
$sum = 42;
echo Yii::t(’app’, ’Balance: {0, number, currency}’, $sum);
Or specify a custom pattern:
$sum = 42;
echo Yii::t(’app’, ’Balance: {0, number, ,000,000000}’, $sum);
11
http://www.php.net/manual/en/intro.intl.php
12
http://icu-project.org/apiref/icu4c/classMessageFormat.html
14.5. INTERNATIONALIZATION 421
Formatting reference13.
echo Yii::t(’app’, ’Today is {0, date}’, time());
Built in formats are short, medium, long, and full:
echo Yii::t(’app’, ’Today is {0, date, short}’, time());
You may also specify a custom pattern:
echo Yii::t(’app’, ’Today is {0, date, yyyy-MM-dd}’, time());
Formatting reference14.
echo Yii::t(’app’, ’It is {0, time}’, time());
Built in formats are short, medium, long, and full:
echo Yii::t(’app’, ’It is {0, time, short}’, time());
You may also specify a custom pattern:
echo Yii::t(’app’, ’It is {0, date, HH:mm}’, time());
Formatting reference15.
echo Yii::t(’app’, ’{n,number} is spelled as {n, spellout}’, [’n’ => 42]);
echo Yii::t(’app’, ’You are {n, ordinal} visitor here!’, [’n’ => 42]);
Will produce “You are 42nd visitor here!”.
echo Yii::t(’app’, ’You are here for {n, duration} already!’, [’n’ => 47]);
Will produce “You are here for 47 sec. already!”.
13
http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html
14
http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html
15
http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html
422 CHAPTER 14. SPECIAL TOPICS
Plurals Different languages have different ways to inflect plurals. Some
rules are very complex so it’s very handy that this functionality is provided
without the need to specify inflection rule. Instead it only requires your
input of inflected words in certain situations.
echo Yii::t(’app’, ’There {n, plural, =0{are no cats} =1{is one cat} other{
are # cats}}!’, [’n’ => 0]);
Will give us “There are no cats!”.
In the plural rule arguments above =0 means exactly zero, =1 stands for
exactly one other is for any other number. # is replaced with the n argument
value. It’s not that simple for languages other than English. Here’s an
example for Russian:
Здесь
{n, plural, котов=0{ нет} есть=1{ один кот} one{# кот} few{# кота} many{#
котов} other{# кота}}!
In the above it worth mentioning that =1 matches exactly n = 1 while one
matches 21 or 101.
Note that if you are using a placeholder twice and one time it’s used as
plural another one should be used as number else you’ll get “Inconsistent types
declared for an argument: U_ARGUMENT_TYPE_MISMATCH” error:
Total {count, number} {count, plural, one{item} other{items}}.
To learn which inflection forms you should specify for your language you can
referrer to the rules reference at unicode.org16.
Selections You can select phrases based on keywords. The pattern in this
case specifies how to map keywords to phrases and provides a default phrase.
echo Yii::t(’app’, ’{name} is {gender} and {gender, select, female{she}
male{he} other{it}} loves Yii!’, [
’name’ => ’Snoopy’,
’gender’ => ’dog’,
]);
Will produce “Snoopy is dog and it loves Yii!”.
In the expression female and male are possible values. other handles values
that do not match. Strings inside brackets are sub-expressions so could be
just a string or a string with more placeholders.
Specifying default translation
You can specify default translations that will be used as a fallback for cat-
egories that don’t match any other translation. This translation should be
marked with *. In order to do it add the following to the application config:
16
http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_
plural_rules.html
14.5. INTERNATIONALIZATION 423
//configure i18n component
’i18n’ => [
’translations’ => [
’*’ => [
’class’ => ’yiii18nPhpMessageSource’
],
],
],
Now you can use categories without configuring each one, which is similar to
Yii 1.1 behavior. Messages for the category will be loaded from a file under
the default translation basePath that is @app/messages:
echo Yii::t(’not_specified_category’, ’message from unspecified category’);
Message will be loaded from @app/messages/<LanguageCode>/not_specified_category
.php.
Translating module messages
If you want to translate messages for a module and avoid using a single
translation file for all messages, you can do it like the following:
<?php
namespace appmodulesusers;
use Yii;
class Module extends yiibaseModule
{
public $controllerNamespace = ’appmodulesuserscontrollers’;
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
Yii::$app->i18n->translations[’modules/users/*’] = [
’class’ => ’yiii18nPhpMessageSource’,
’sourceLanguage’ => ’en-US’,
’basePath’ => ’@app/modules/users/messages’,
’fileMap’ => [
’modules/users/validation’ => ’validation.php’,
’modules/users/form’ => ’form.php’,
...
],
];
}
424 CHAPTER 14. SPECIAL TOPICS
public static function t($category, $message, $params = [], $language =
null)
{
return Yii::t(’modules/users/’ . $category, $message, $params,
$language);
}
}
In the example above we are using wildcard for matching and then filtering
each category per needed file. Instead of using fileMap you can simply use
convention of category mapping to the same named file and use Module::t
(’validation’, ’your custom validation message’) or Module::t(’form’, ’some
form label’) directly.
Translating widgets messages
The same rule as applied for Modules above can be applied for widgets too,
for example:
<?php
namespace appwidgetsmenu;
use yiibaseWidget;
use Yii;
class Menu extends Widget
{
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
$i18n = Yii::$app->i18n;
$i18n->translations[’widgets/menu/*’] = [
’class’ => ’yiii18nPhpMessageSource’,
’sourceLanguage’ => ’en-US’,
’basePath’ => ’@app/widgets/menu/messages’,
’fileMap’ => [
’widgets/menu/messages’ => ’messages.php’,
],
];
}
public function run()
{
echo $this->render(’index’);
}
14.5. INTERNATIONALIZATION 425
public static function t($category, $message, $params = [], $language =
null)
{
return Yii::t(’widgets/menu/’ . $category, $message, $params,
$language);
}
}
Instead of using fileMap you can simply use convention of category mapping
to the same named file and use Menu::t(’messages’, ’new messages {messages}
’, [’{messages}’ => 10]) directly.
Note: For widgets you also can use i18n views, same rules as for
controllers are applied to them too.
Translating framework messages
Yii comes with default translation messages for validation errors and some
other strings. These messages are all in the category yii. Sometimes you
want to correct default framework message translation for your application.
In order to do so configure the i18n application component like the following:
’i18n’ => [
’translations’ => [
’yii’ => [
’class’ => ’yiii18nPhpMessageSource’,
’sourceLanguage’ => ’en-US’,
’basePath’ => ’@app/messages’
],
],
],
Now you can place your adjusted translations to @app/messages/<language>/
yii.php.
Handling missing translations
If the translation is missing at the source, Yii displays the requested message
content. Such behavior is very convenient in case your raw message is a valid
verbose text. However, sometimes it is not enough. You may need to perform
some custom processing of the situation, when requested translation is miss-
ing at the source. This can be achieved using the yiii18nMessageSource
::EVENT_MISSING_TRANSLATION-event of yiii18nMessageSource.
For example to mark all missing translations with something notable, so
they can be easily found at the page we first we need to setup event handler.
This can be done in the application configuration:
’components’ => [
426 CHAPTER 14. SPECIAL TOPICS
// ...
’i18n’ => [
’translations’ => [
’app*’ => [
’class’ => ’yiii18nPhpMessageSource’,
’fileMap’ => [
’app’ => ’app.php’,
’app/error’ => ’error.php’,
],
’on missingTranslation’ => [’appcomponents
TranslationEventHandler’, ’handleMissingTranslation’]
],
],
],
],
Now we need to implement our own event handler:
<?php
namespace appcomponents;
use yiii18nMissingTranslationEvent;
class TranslationEventHandler
{
public static function(MissingTranslationEvent $event) {
$event->translatedMessage = "@MISSING: {$event->category}.{$event->
message} FOR LANGUAGE {$event->language} @";
}
}
If yiii18nMissingTranslationEvent::translatedMessage is set by the
event handler it will be displayed as the translation result.
Attention: each message source handles its missing translations
separately. If you are using several message sources and wish
them treat missing translation in the same way, you should assign
corresponding event handler to each of them.
14.5.3 Views
Instead of translating messages as described in the last section, you can
also use i18n in your views to provide support for different languages. For
example, if you have a view views/site/index.php and you want to create a
special version for russian language of it, you create a ru-RU folder under
the view path of the current controller/widget and put the file for russian
language as follows views/site/ru-RU/index.php. Yii will then load the file for
the current language if it exists and fall back to the original view file if none
was found.
14.5. INTERNATIONALIZATION 427
Note: If language is specified as en-US and there are no corres-
ponding views, Yii will try views under en before using original
ones.
14.5.4 Formatting Number and Date values
See the data formatter section for details.
14.5.5 Setting up your PHP environment
Yii uses the PHP intl extension17 to provide most of its internationalization
features such as the number and date formatting of the yiii18nFormatter
class and the message formatting using yiii18nMessageFormatter. Both
classes provides a fallback implementation that provides basic functionality
in case intl is not installed. This fallback implementation however only works
well for sites in english language and even there can not provide the rich set
of features that is avialable with the PHP intl extension, so its installation
is highly recommended.
The PHP intl extension18 is based on the ICU library19 which provides
the knowledge and formatting rules for all the different locales. According to
this fact the formatting of dates and numbers and also the supported syntax
available for message formatting differs between different versions of the ICU
library that is compiled with you PHP binary.
To ensure your website works with the same output in all environments
it is recommended to install the PHP intl extension in all environments and
verify that the version of the ICU library compiled with PHP is the same.
To find out which version of ICU is used by PHP you can run the following
script, which will give you the PHP and ICU version used.
<?php
echo "PHP: " . PHP_VERSION . "n";
echo "ICU: " . INTL_ICU_VERSION . "n";
We recommend an ICU version greater or equal to version ICU 49 to be
able to use all the features described in this document. One major feature
that is missing in Versions below 49 is the # placeholder in plural rules.
See http://site.icu-project.org/download for a list of available ICU
versions. Note that the version numbering has changed after the 4.8 release
so that the first digits are now merged: the sequence is ICU 4.8, ICU 49,
ICU 50.
17
http://php.net/manual/en/book.intl.php
18
http://php.net/manual/en/book.intl.php
19
http://site.icu-project.org/
428 CHAPTER 14. SPECIAL TOPICS
14.6 Mailing
Note: This section is under development.
Yii supports composition and sending of the email messages. However, the
framework core provides only the content composition functionality and basic
interface. Actual mail sending mechanism should be provided by the exten-
sion, because different projects may require its different implementation and
it usually depends on the external services and libraries.
For the most common cases you can use yii2-swiftmailer20 official exten-
sion.
14.6.1 Configuration
Mail component configuration depends on the extension you have chosen. In
general your application configuration should look like:
return [
//....
’components’ => [
’mailer’ => [
’class’ => ’yiiswiftmailerMailer’,
],
],
];
14.6.2 Basic usage
Once ‘mailer’ component is configured, you can use the following code to
send an email message:
Yii::$app->mailer->compose()
->setFrom(’from@domain.com’)
->setTo(’to@domain.com’)
->setSubject(’Message subject’)
->setTextBody(’Plain text content’)
->setHtmlBody(’<b>HTML content</b>’)
->send();
In above example method compose() creates an instance of the mail message,
which then is populated and sent. You may put more complex logic in this
process if needed:
$message = Yii::$app->mailer->compose();
if (Yii::$app->user->isGuest) {
$message->setFrom(’from@domain.com’)
} else {
$message->setFrom(Yii::$app->user->identity->email)
}
20
https://github.com/yiisoft/yii2/tree/master/extensions/swiftmailer
14.6. MAILING 429
$message->setTo(Yii::$app->params[’adminEmail’])
->setSubject(’Message subject’)
->setTextBody(’Plain text content’)
->send();
Note: each ‘mailer’ extension comes in 2 major classes: ‘Mailer’
and ‘Message’. ‘Mailer’ always knows the class name and specific
of the ‘Message’. Do not attempt ot instantiate ‘Message’ object
directly - always use compose() method for it.
You may also send several messages at once:
$messages = [];
foreach ($users as $user) {
$messages[] = Yii::$app->mailer->compose()
// ...
->setTo($user->email);
}
Yii::$app->mailer->sendMultiple($messages);
Some particular mail extensions may benefit from this approach, using single
network message etc.
14.6.3 Composing mail content
Yii allows composition of the actual mail messages content via special view
files. By default these files should be located at ‘@app/mail’ path.
Example mail view file content:
<?php
use yiihelpersHtml;
use yiihelpersUrl;
/* @var $this yiiwebView view component instance */
/* @var $message yiimailBaseMessage instance of newly created mail
message */
?>
<h2>This message allows you to visit out site home page by one click</h2>
<?= Html::a(’Go to home page’, Url::home(’http’)) ?>
In order to compose message content via view file simply pass view name to
the compose() method:
Yii::$app->mailer->compose(’home-link’) // message body becomes a view
rendering result here
->setFrom(’from@domain.com’)
->setTo(’to@domain.com’)
->setSubject(’Message subject’)
->send();
430 CHAPTER 14. SPECIAL TOPICS
You may pass additional view parameters to compose() method, which will
be available inside the view files:
Yii::$app->mailer->compose(’greetings’, [
’user’ => Yii::$app->user->identity,
’advertisement’ => $adContent,
]);
You can specify different view files for HTML and plain text message con-
tents:
Yii::$app->mailer->compose([
’html’ => ’contact-html’,
’text’ => ’contact-text’,
]);
If you specify view name as a scalar string, its rendering result will be used
as HTML body, while plain text body will be composed by removing all
HTML entities from HTML one.
View rendering result can be wrapped into the layout, which an be setup
using yiimailBaseMailer::htmlLayout and yiimailBaseMailer::textLayout.
It will work the same way like layouts in regular web application. Layout
can be used to setup mail CSS styles or other shared content:
<?php
use yiihelpersHtml;
/* @var $this yiiwebView view component instance */
/* @var $message yiimailMessageInterface the message being composed */
/* @var $content string main view render result */
?>
<?php $this->beginPage() ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/
TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::
$app->charset ?>" />
<style type="text/css">
.heading {...}
.list {...}
.footer {...}
</style>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<?= $content ?>
<div class="footer">With kind regards, <?= Yii::$app->name ?> team</div>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>
14.6. MAILING 431
14.6.4 File attachment
You can add attachments to message using methods attach() and attachContent
():
$message = Yii::$app->mailer->compose();
// Attach file from local file system:
$message->attach(’/path/to/source/file.pdf’);
// Create attachment on-the-fly
$message->attachContent(’Attachment content’, [’fileName’ => ’attach.txt’, ’
contentType’ => ’text/plain’]);
14.6.5 Embed images
You can embed images into the message content using embed() method. This
method returns the attachment id, which should be then used at ‘img’ tag.
This method is easy to use when composing message content via view file:
Yii::$app->mailer->compose(’embed-email’, [’imageFileName’ => ’/path/to/
image.jpg’])
// ...
->send();
Then inside view file you can use following code:
<img src="<?= $message->embed($imageFileName); ?>">
14.6.6 Testing and debugging
Developer often a to check, what actual emails are sent by application, what
was their content and so on. Such ability is granted by Yii via yiimail
BaseMailer::useFileTransport. If enabled, this option enforces saving mail
message data into the local files instead of regular sending. These files will
be saved under yiimailBaseMailer::fileTransportPath, which is ‘@runtime/-
mail’ by default.
Note: you can either save messages to the file or send them to
actual recipients, but can not do both simultaneously.
Mail message file can be opened by regular text file editor, so you can browse
actual message headers, content and so on. This mechanism amy prove itself,
while debugging application or running unit test.
Note: mail message file content is composed via yiimailMessageInterface
::toString(), so it depends on actual mail extension you are using
in your application.
432 CHAPTER 14. SPECIAL TOPICS
14.6.7 Creating your own mail solution
In order to create your own custom mail solution, you need to create 2
classes: one for the ‘Mailer’ and another one for the ‘Message’. You can
use yiimailBaseMailer and yiimailBaseMessage as a base classes for your
solution. These classes already contains basic logic, which is described in
this guide. However, their usage is not mandatory, it is enough to implement
yiimailMailerInterface and yiimailMessageInterface interfaces. Then you
need to implement all abstract methods to build you solution.
14.7 Performance Tuning
Note: This section is under development.
The performance of your web application is based upon two parts. First is
the framework performance and the second is the application itself. Yii has a
pretty low performance impact on your application out of the box and can be
fine-tuned further for production environment. As for the application, we’ll
provide some of the best practices along with examples on how to apply
them to Yii.
14.7.1 Preparing environment
A well configured environment to run PHP application really matters. In
order to get maximum performance:
• Always use latest stable PHP version. Each major release brings sig-
nificant performance improvements and reduced memory usage.
• Use APC21 for PHP 5.4 and less or Opcache22 for PHP 5.5 and more.
It gives a very good performance boost.
14.7.2 Preparing framework for production
Disabling Debug Mode
First thing you should do before deploying your application to production
environment is to disable debug mode. A Yii application runs in debug mode
if the constant YII_DEBUG is defined as true in index.php so to disable debug
the following should be in your index.php:
defined(’YII_DEBUG’) or define(’YII_DEBUG’, false);
21
http://ru2.php.net/apc
22
http://php.net/opcache
14.7. PERFORMANCE TUNING 433
Debug mode is very useful during development stage, but it would impact
performance because some components cause extra burden in debug mode.
For example, the message logger may record additional debug information
for every message being logged.
Enabling PHP opcode cache
Enabling the PHP opcode cache improves any PHP application performance
and lowers memory usage significantly. Yii is no exception. It was tested
with both PHP 5.5 OPcache23 and APC PHP extension24. Both cache and
optimize PHP intermediate code and avoid the time spent in parsing PHP
scripts for every incoming request.
Turning on ActiveRecord database schema caching
If the application is using Active Record, we should turn on the schema
caching to save the time of parsing database schema. This can be done by
setting the Connection::enableSchemaCache property to be true via application
configuration protected/config/main.php:
return [
// ...
’components’ => [
// ...
’db’ => [
’class’ => ’yiidbConnection’,
’dsn’ => ’mysql:host=localhost;dbname=mydatabase’,
’username’ => ’root’,
’password’ => ’’,
’enableSchemaCache’ => true,
// Duration of schema cache.
// ’schemaCacheDuration’ => 3600,
// Name of the cache component used. Default is ’cache’.
//’schemaCache’ => ’cache’,
],
’cache’ => [
’class’ => ’yiicachingFileCache’,
],
],
];
Note that cache application component should be configured.
23
http://php.net/manual/en/book.opcache.php
24
http://php.net/manual/en/book.apc.php
434 CHAPTER 14. SPECIAL TOPICS
Combining and Minimizing Assets
It is possible to combine and minimize assets, typically JavaScript and CSS,
in order to slightly improve page load time and therefore deliver better ex-
perience for end user of your application.
In order to learn how it can be achieved, refer to assets guide section.
Using better storage for sessions
By default PHP uses files to handle sessions. It is OK for development and
small projects but when it comes to handling concurrent requests it’s better
to switch to another storage such as database. You can do so by configuring
your application via protected/config/main.php:
return [
// ...
’components’ => [
’session’ => [
’class’ => ’yiiwebDbSession’,
// Set the following if want to use DB component other than
// default ’db’.
// ’db’ => ’mydb’,
// To override default session table set the following
// ’sessionTable’ => ’my_session’,
],
],
];
You can use CacheSession to store sessions using cache. Note that some cache
storage such as memcached has no guarantee that session data will not be
lost leading to unexpected logouts.
If you have Redis25 on your server, it’s highly recommended as session
storage.
14.7.3 Improving application
Using Serverside Caching Techniques
As described in the Caching section, Yii provides several caching solutions
that may improve the performance of a Web application significantly. If the
generation of some data takes long time, we can use the data caching ap-
proach to reduce the data generation frequency; If a portion of page remains
relatively static, we can use the fragment caching approach to reduce its
rendering frequency; If a whole page remains relative static, we can use the
page caching approach to save the rendering cost for the whole page.
25
http://redis.io/
14.7. PERFORMANCE TUNING 435
Leveraging HTTP to save processing time and bandwidth
Leveraging HTTP caching saves both processing time, bandwidth and re-
sources significantly. It can be implemented by sending either ETag or Last-
Modified header in your application response. If browser is implemented ac-
cording to HTTP specification (most browsers are), content will be fetched
only if it is different from what it was prevously.
Forming proper headers is time consuming task so Yii provides a shortcut
in form of controller filter yiifiltersHttpCache. Using it is very easy.
In a controller you need to implement behaviors method like the following:
public function behaviors()
{
return [
’httpCache’ => [
’class’ => yiifiltersHttpCache::className(),
’only’ => [’list’],
’lastModified’ => function ($action, $params) {
$q = new Query();
return strtotime($q->from(’users’)->max(’updated_timestamp’)
);
},
// ’etagSeed’ => function ($action, $params) {
// return // generate etag seed here
//}
],
];
}
In the code above one can use either etagSeed or lastModified. Implementing
both isn’t necessary. The goal is to determine if content was modified in a
way that is cheaper than fetching and rendering that content. lastModified
should return unix timestamp of the last content modification while etagSeed
should return a string that is then used to generate ETag header value.
Database Optimization
Fetching data from database is often the main performance bottleneck in
a Web application. Although using caching may alleviate the performance
hit, it does not fully solve the problem. When the database contains enorm-
ous data and the cached data is invalid, fetching the latest data could be
prohibitively expensive without proper database and query design.
Design index wisely in a database. Indexing can make SELECT queries
much faster, but it may slow down INSERT, UPDATE or DELETE queries.
For complex queries, it is recommended to create a database view for
it instead of issuing the queries inside the PHP code and asking DBMS to
parse them repetitively.
Do not overuse Active Record. Although Active Record is good at mod-
eling data in an OOP fashion, it actually degrades performance due to the
436 CHAPTER 14. SPECIAL TOPICS
fact that it needs to create one or several objects to represent each row of
query result. For data intensive applications, using DAO or database APIs
at lower level could be a better choice.
Last but not least, use LIMIT in your SELECT queries. This avoids fetching
overwhelming data from database and exhausting the memory allocated to
PHP.
Using asArray
A good way to save memory and processing time on read-only pages is to
use ActiveRecord’s asArray method.
class PostController extends Controller
{
public function actionIndex()
{
$posts = Post::find()->orderBy(’id DESC’)->limit(100)->asArray()->
all();
return $this->render(’index’, [’posts’ => $posts]);
}
}
In the view you should access fields of each individual record from $posts as
array:
foreach ($posts as $post) {
echo $post[’title’]."<br>";
}
Note that you can use array notation even if asArray wasn’t specified and
you’re working with AR objects.
Processing data in background
In order to respond to user requests faster you can process heavy parts of
the request later if there’s no need for immediate response.
• Cron jobs + console.
• queues + handlers.
TBD
If nothing helps
If nothing helps, never assume what may fix performance problem. Always
profile your code instead before changing anything. The following tools may
be helpful:
• Yii debug toolbar and debugger
14.7. PERFORMANCE TUNING 437
• XDebug profiler26
• XHProf27
26
http://xdebug.org/docs/profiler
27
http://www.php.net/manual/en/book.xhprof.php
438 CHAPTER 14. SPECIAL TOPICS
Error: not existing file: tutorial-shared-hosting.md
14.8. USING TEMPLATE ENGINES 439
14.8 Using template engines
Note: This section is under development.
By default, Yii uses PHP as its template language, but you can configure
Yii to support other rendering engines, such as Twig28 or Smarty29.
The view component is responsible for rendering views. You can add a
custom template engine by reconfiguring this component’s behavior:
[
’components’ => [
’view’ => [
’class’ => ’yiiwebView’,
’renderers’ => [
’tpl’ => [
’class’ => ’yiismartyViewRenderer’,
//’cachePath’ => ’@runtime/Smarty/cache’,
],
’twig’ => [
’class’ => ’yiitwigViewRenderer’,
//’cachePath’ => ’@runtime/Twig/cache’,
//’options’ => [], /* Array of twig options */
’globals’ => [’html’ => ’yiihelpersHtml’],
’uses’ => [’yiibootstrap’],
],
// ...
],
],
],
]
In the code above, both Smarty and Twig are configured to be useable by
the view files. But in order to get these extensions into your project, you
need to also modify your composer.json file to include them, too:
"yiisoft/yii2-smarty": "*",
"yiisoft/yii2-twig": "*",
That code would be added to the require section of composer.json. After
making that change and saving the file, you can install the extensions by
running composer update --prefer-dist in the command-line.
14.8.1 Twig
To use Twig, you need to create templates in files that have the .twig exten-
sion (or use another file extension but configure the component accordingly).
Unlike standard view files, when using Twig you must include the extension
in your $this->render() controller call:
return $this->render(’renderer.twig’, [’username’ => ’Alex’]);
28
http://twig.sensiolabs.org/
29
http://www.smarty.net/
440 CHAPTER 14. SPECIAL TOPICS
Template syntax
The best resource to learn Twig basics is its official documentation you can
find at twig.sensiolabs.org30. Additionally there are Yii-specific syntax ex-
tensions described below.
Method and function calls If you need result you can call a method or
a function using the following syntax:
{% set result = my_function({’a’ : ’b’}) %}
{% set result = myObject.my_function({’a’ : ’b’}) %}
If you need to echo result instead of assigning it to a variable:
{{ my_function({’a’ : ’b’}) }}
{{ myObject.my_function({’a’ : ’b’}) }}
In case you don’t need result you shoud use void wrapper:
{{ void(my_function({’a’ : ’b’})) }}
{{ void(myObject.my_function({’a’ : ’b’})) }}
Setting object properties There’s a special function called set that al-
lows you to set property of an object. For example, the following in the
template will change page title:
{{ set(this, ’title’, ’New title’) }}
Importing namespaces and classes You can import additional classes
and namespaces right in the template:
Namespace import:
{{ use(’/app/widgets’) }}
Class import:
{{ use(’/yii/widgets/ActiveForm’) }}
Aliased class import:
{{ use({’alias’ => ’/app/widgets/MyWidget’}) }}
Referencing other templates There are two ways of referencing tem-
plates in include and extends statements:
{% include "comment.twig" %}
{% extends "post.twig" %}
{% include "@app/views/snippets/avatar.twig" %}
{% extends "@app/views/layouts/2columns.twig" %}
30
http://twig.sensiolabs.org/documentation
14.8. USING TEMPLATE ENGINES 441
In the first case the view will be searched relatively to the current template
path. For comment.twig and post.twig that means these will be searched in
the same directory as the currently rendered template.
In the second case we’re using path aliases. All the Yii aliases such as
@app are available by default.
Widgets Extension helps using widgets in convenient way converting their
syntax to function calls:
{{ use(’yii/bootstrap’) }}
{{ nav_bar_begin({
’brandLabel’: ’My Company’,
}) }}
{{ nav_widget({
’options’: {
’class’: ’navbar-nav navbar-right’,
},
’items’: [{
’label’: ’Home’,
’url’: ’/site/index’,
}]
}) }}
{{ nav_bar_end() }}
In the template above nav_bar_begin, nav_bar_end or nav_widget consists of
two parts. First part is widget name coverted to lowercase and underscores:
NavBar becomes nav_bar, Nav becomes nav. _begin, _end and _widget are the
same as ::begin(), ::end() and ::widget() calls of a widget.
One could also use more generic widget_end() that executes Widget::end().
Assets Assets could be registered the following way:
{{ use(’yii/web/JqueryAsset’) }}
{{ register_jquery_asset() }}
In the call above register identifies that we’re working with assets while
jquery_asset translates to JqueryAsset class that we’ve already imported with
use.
Forms You can build forms the following way:
{{ use(’yii/widgets/ActiveForm’) }}
{% set form = active_form_begin({
’id’ : ’login-form’,
’options’ : {’class’ : ’form-horizontal’},
}) %}
{{ form.field(model, ’username’) | raw }}
{{ form.field(model, ’password’).passwordInput() | raw }}
<div class="form-group">
<input type="submit" value="Login" class="btn btn-primary" />
442 CHAPTER 14. SPECIAL TOPICS
</div>
{{ active_form_end() }}
URLs There are two functions you can use for building URLs:
<a href="{{ path(’blog/view’, {’alias’ : post.alias}) }}">{{ post.title }}</
a>
<a href="{{ url(’blog/view’, {’alias’ : post.alias}) }}">{{ post.title }}</a
>
path generates relative URL while url generates absolute one. Internally both
are using yiihelpersUrl.
Additional variables Within Twig templates the following variables are
always defined:
• app, which equates to Yii::$app
• this, which equates to the current View object
Additional configuration
Yii Twig extension allows you to define your own syntax and bring regular
helper classes into templates. Let’s review configuration options.
Globals You can add global helpers or values via the application config-
uration’s globals variable. You can define both Yii helpers and your own
variables there:
’globals’ => [
’html’ => ’yiihelpersHtml’,
’name’ => ’Carsten’,
’GridView’ => ’yiigridGridView’,
],
Once configured, in your template you can use the globals in the following
way:
Hello, {{name}}! {{ html.a(’Please login’, ’site/login’) | raw }}.
{{ GridView.widget({’dataProvider’ : provider}) | raw }}
Functions You can define additional functions like the following:
’functions’ => [
’rot13’ => ’str_rot13’,
’truncate’ => ’yiihelpersStringHelper::truncate’,
],
In template they could be used like the following:
‘{{ rot13(’test’) }}‘
‘{{ truncate(post.text, 100) }}‘
14.8. USING TEMPLATE ENGINES 443
Filters Additional filters may be added via the application configuration’s
filters option:
’filters’ => [
’jsonEncode’ => ’yiihelpersJson::encode’,
],
Then in the template you can apply filter using the following syntax:
{{ model|jsonEncode }}
14.8.2 Smarty
To use Smarty, you need to create templates in files that have the .tpl exten-
sion (or use another file extension but configure the component accordingly).
Unlike standard view files, when using Smarty you must include the exten-
sion in your $this->render() or $this->renderPartial() controller calls:
return $this->render(’renderer.tpl’, [’username’ => ’Alex’]);
Template syntax
The best resource to learn Smarty template syntax is its official document-
ation you can find at www.smarty.net31. Additionally there are Yii-specific
syntax extensions described below.
Setting object properties There’s a special function called set that al-
lows you to set common properties of the view and controller. Currently
available properties are title, theme and layout:
{set title="My Page"}
{set theme="frontend"}
{set layout="main.tpl"}
For title there’s dedicated block as well:
{title}My Page{/title}
Setting meta tags Meta tags could be set like to following:
{meta keywords="Yii,PHP,Smarty,framework"}
There’s also dedicated block for description:
{description}This is my page about Smarty extension{/description}
Calling object methods Sometimes you need calling
31
http://www.smarty.net/docs/en/
444 CHAPTER 14. SPECIAL TOPICS
Importing static classes, using widgets as functions and blocks
You can import additional static classes right in the template:
{use class="yiihelpersHtml"}
{Html::mailto(’eugenia@example.com’)}
If you want you can set custom alias:
{use class="yiihelpersHtml" as="Markup"}
{Markup::mailto(’eugenia@example.com’)}
Extension helps using widgets in convenient way converting their syntax to
function calls or blocks. For regular widgets function could be used like the
following:
{use class=’@yiigridGridView’ type=’function’}
{GridView dataProvider=$provider}
For widgets with begin and end methods such as ActiveForm it’s better to
use block:
{use class=’yiiwidgetsActiveForm’ type=’block’}
{ActiveForm assign=’form’ id=’login-form’ action=’/form-handler’ options=[’
class’ => ’form-horizontal’]}
{$form->field($model, ’firstName’)}
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</div>
{/ActiveForm}
If you’re using particular widget a lot, it is a good idea to declare it in
application config and remove {use class call from templates:
’components’ => [
’view’ => [
// ...
’renderers’ => [
’tpl’ => [
’class’ => ’yiismartyViewRenderer’,
’widgets’ => [
’blocks’ => [
’ActiveForm’ => ’yiiwidgetsActiveForm’,
],
],
],
],
],
],
Referencing other templates There are two main ways of referencing
templates in include and extends statements:
14.8. USING TEMPLATE ENGINES 445
{include ’comment.tpl’}
{extends ’post.tpl’}
{include ’@app/views/snippets/avatar.tpl’}
{extends ’@app/views/layouts/2columns.tpl’}
In the first case the view will be searched relatively to the current template
path. For comment.tpl and post.tpl that means these will be searched in the
same directory as the currently rendered template.
In the second case we’re using path aliases. All the Yii aliases such as
@app are available by default.
CSS, JavaScript and asset bundles In order to register JavaScript and
CSS files the following syntax could be used:
{registerJsFile url=’http://maps.google.com/maps/api/js?sensor=false’
position=’POS_END’}
{registerCssFile url=’@assets/css/normalizer.css’}
If you need JavaScript and CSS directly in the template there are convenient
blocks:
{registerJs key=’show’ position=’POS_LOAD’}
$("span.show").replaceWith(’<div class="show">’);
{/registerJs}
{registerCss}
div.header {
background-color: #3366bd;
color: white;
}
{/registerCss}
Asset bundles could be registered the following way:
{use class="yiiwebJqueryAsset"}
{JqueryAsset::register($this)|void}
Here we’re using void modifier because we don’t need method call result.
URLs There are two functions you can use for building URLs:
<a href="{path route=’blog/view’ alias=$post.alias}">{$post.title}</a>
<a href="{url route=’blog/view’ alias=$post.alias}">{$post.title}</a>
path generates relative URL while url generates absolute one. Internally both
are using yiihelpersUrl.
Additional variables Within Smarty templates the following variables
are always defined:
• $app, which equates to Yii::$app
• $this, which equates to the current View object
446 CHAPTER 14. SPECIAL TOPICS
Accessing config params Yii parameters that are available in your ap-
plication through Yii::$app->params->something could be used the following
way:
‘{#something#}‘
14.9 Working with Third-Party Code
From time to time, you may need to use some third-party code in your Yii
applications. Or you may want to use Yii as a library in some third-party
systems. In this section, we will show how to achieve these goals.
14.9.1 Using Third-Party Libraries in Yii
To use a third-party library in a Yii application, you mainly need to make
sure the classes in the library are properly included or can be autoloaded.
Using Composer Packages
Many third-party libraries are released in terms of Composer32 packages.
You can install such libraries by taking the following two simple steps:
1. modify the composer.json file of your application and specify which
Composer packages you want to install.
2. run php composer.phar install to install the specified packages.
The classes in the installed Composer packages can be autoloaded using
the Composer autoloader. Make sure the entry script of your application
contains the following lines to install the Composer autoloader:
// install Composer autoloader
require(__DIR__ . ’/../vendor/autoload.php’);
// include Yii class file
require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’);
Using Downloaded Libraries
If a library is not released as a Composer package, you should follow its
installation instructions to install it. In most cases, you will need to download
a release file manually and unpack it in the BasePath/vendor directory, where
BasePath represents the base path of your application.
If a library carries its own class autoloader, you may install it in the
entry script of your application. It is recommended the installation is done
32
https://getcomposer.org/
14.9. WORKING WITH THIRD-PARTY CODE 447
before you include the Yii.php file so that the Yii class autoloader can take
precedence in autoloading classes.
If a library does not provide a class autoloader, but its class naming fol-
lows PSR-433, you may use the Yii class autoloader to autoload the classes.
All you need to do is just to declare a root alias for each root namespace used
in its classes. For example, assume you have installed a library in the direct-
ory vendor/foo/bar, and the library classes are under the xyz root namespace.
You can include the following code in your application configuration:
[
’aliases’ => [
’@xyz’ => ’@vendor/foo/bar’,
],
]
If neither of the above is the case, it is likely that the library relies on PHP
include path configuration to correctly locate and include class files. Simply
follow its instruction on how to configure the PHP include path.
In the worst case when the library requires explicitly including every class
file, you can use the following method to include the classes on demand:
• Identify which classes the library contains.
• List the classes and the corresponding file paths in Yii::$classMap in
the entry script of the application. For example,
Yii::$classMap[’Class1’] = ’path/to/Class1.php’;
Yii::$classMap[’Class2’] = ’path/to/Class2.php’;
14.9.2 Using Yii in Third-Party Systems
Because Yii provides many excellent features, sometimes you may want to
use some of its features to support developing or enhancing 3rd-party sys-
tems, such as WordPress, Joomla, or applications developed using other
PHP frameworks. For example, you may want to use the yiihelpers
ArrayHelper class or use the Active Record feature in a third-party sys-
tem. To achieve this goal, you mainly need to take two steps: install Yii,
and bootstrap Yii.
If the third-party system uses Composer to manage its dependencies, you
can simply run the following commands to install Yii:
php composer.phar require yiisoft/yii2-framework:*
php composer.phar install
Otherwise, you can download34 the Yii release file and unpack it in the
BasePath/vendor directory.
33
http://www.php-fig.org/psr/psr-4/
34
http://www.yiiframework.com/download/
448 CHAPTER 14. SPECIAL TOPICS
Next, you should modify the entry script of the 3rd-party system by
including the following code at the beginning:
require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’);
$yiiConfig = require(__DIR__ . ’/../config/yii/web.php’);
new yiiwebApplication($yiiConfig); // Do NOT call run() here
As you can see, the code above is very similar to that in the entry script of
a typical Yii application. The only difference is that after the application
instance is created, the run() method is not called. This is because by calling
run(), Yii will take over the control of the request handling workflow.
Like in a Yii application, you should configure the application instance
based on the environment running the third-party system. For example,
to use the Active Record feature, you need to configure the db application
component with the DB connection setting used by the third-party system.
Now you can use most features provided by Yii. For example, you can
create Active Record classes and use them to work with databases.
14.9.3 Using Yii 2 with Yii 1
If you were using Yii 1 previously, it is likely you have a running Yii 1
application. Instead of rewriting the whole application in Yii 2, you may
just want to enhance it using some of the features only available in Yii 2.
This can be achieved as described below.
Note: Yii 2 requires PHP 5.4 or above. You should make sure
that both your server and the existing application support this.
First, install Yii 2 in your existing application by following the instructions
given in the last subsection.
Second, modify the entry script of the application as follows,
// include the customized Yii class described below
require(__DIR__ . ’/../components/Yii.php’);
// configuration for Yii 2 application
$yii2Config = require(__DIR__ . ’/../config/yii2/web.php’);
new yiiwebApplication($yii2Config); // Do NOT call run()
// configuration for Yii 1 application
$yii1Config = require(__DIR__ . ’/../config/yii1/main.php’);
Yii::createWebApplication($yii1Config)->run();
Because both Yii 1 and Yii 2 have the Yii class, you should create a custom-
ized version to combine them. The above code includes the customized Yii
class file, which can be created as follows.
$yii2path = ’/path/to/yii2’;
require($yii2path . ’/BaseYii.php’); // Yii 2.x
14.9. WORKING WITH THIRD-PARTY CODE 449
$yii1path = ’/path/to/yii1’;
require($yii1path . ’/YiiBase.php’); // Yii 1.x
class Yii extends yiiBaseYii
{
// copy-paste the code in YiiBase (1.x) here
}
Yii::$classMap = include($yii2path . ’/classes.php’);
// register Yii2 autoloader via Yii1
Yii::registerAutoloader([’Yii’, ’autoload’]);
That’s all! Now in any part of your code, you can use Yii::$app to access the
Yii 2 application instance, while Yii::app() will give you the Yii 1 application
instance:
echo get_class(Yii::app()); // outputs ’CWebApplication’
echo get_class(Yii::$app); // outputs ’yiiwebApplication’
450 CHAPTER 14. SPECIAL TOPICS
Chapter 15
Widgets
15.1 Bootstrap Widgets
Note: This section is under development.
Out of the box, Yii includes support for the Bootstrap 31 markup and com-
ponents framework (also known as “Twitter Bootstrap”). Bootstrap is an
excellent, responsive framework that can greatly speed up the client-side of
your development process.
The core of Bootstrap is represented by two parts:
• CSS basics, such as a grid layout system, typography, helper classes,
and responsive utilities.
• Ready to use components, such as forms, menus, pagination, modal
boxes, tabs etc.
15.1.1 Basics
Yii doesn’t wrap the bootstrap basics into PHP code since HTML is very
simple by itself in this case. You can find details about using the basics at
bootstrap documentation website2. Still Yii provides a convenient way to
include bootstrap assets in your pages with a single line added to AppAsset.php
located in your @app/assets directory:
public $depends = [
’yiiwebYiiAsset’,
’yiibootstrapBootstrapAsset’, // this line
];
Using bootstrap through Yii asset manager allows you to minimize its re-
sources and combine with your own resources when needed.
1
http://getbootstrap.com/
2
http://getbootstrap.com/css/
451
452 CHAPTER 15. WIDGETS
15.1.2 Yii widgets
Most complex bootstrap components are wrapped into Yii widgets to al-
low more robust syntax and integrate with framework features. All widgets
belong to yiibootstrap namespace:
• yiibootstrapActiveForm
• yiibootstrapAlert
• yiibootstrapButton
• yiibootstrapButtonDropdown
• yiibootstrapButtonGroup
• yiibootstrapCarousel
• yiibootstrapCollapse
• yiibootstrapDropdown
• yiibootstrapModal
• yiibootstrapNav
• yiibootstrapNavBar
• yiibootstrapProgress
• yiibootstrapTabs
15.1.3 Using the .less files of Bootstrap directly
If you want to include the Bootstrap css directly in your less files3 you
may need to disable the original bootstrap css files to be loaded. You can
do this by setting the css property of the yiibootstrapBootstrapAsset
to be empty. For this you need to configure the assetManager application
component as follows:
’assetManager’ => [
’bundles’ => [
’yiibootstrapBootstrapAsset’ => [
’css’ => [],
]
]
]
3
http://getbootstrap.com/getting-started/#customizing
15.2. JQUERY UI WIDGETS 453
15.2 Jquery UI Widgets
Note: This section is under development.
Out of the box, Yii includes support for the jQuery UI4 library. jQuery UI
is a curated set of user interface interactions, effects, widgets, and themes
built on top of the jQuery JavaScript Library.
15.2.1 Yii widgets
Most complex jQuery UI components are wrapped into Yii widgets to al-
low more robust syntax and integrate with framework features. All widgets
belong to yiijui namespace:
• yiijuiAccordion
• yiijuiAutoComplete
• yiijuiDatePicker
• yiijuiDialog
• yiijuiDraggable
• yiijuiDroppable
• yiijuiMenu
• yiijuiProgressBar
• yiijuiResizable
• yiijuiSelectable
• yiijuiSlider
• yiijuiSliderInput
• yiijuiSortable
• yiijuiSpinner
• yiijuiTabs
4
http://api.jqueryui.com/
454 CHAPTER 15. WIDGETS
Chapter 16
Helpers
16.1 Helpers
Note: This section is under development.
Yii provides many classes that help simplify common coding tasks, such as
string or array manipulations, HTML code generation, and so on. These
helper classes are organized under the yiihelpers namespace and are all
static classes (meaning they contain only static properties and methods and
should not be instantiated).
You use a helper class by directly calling one of its static methods, like
the following:
use yiihelpersHtml;
echo Html::encode(’Test > test’);
Note: To support extending helper classes, Yii breaks each core
helper class into two classes: a base class (e.g. BaseArrayHelper)
and a concrete class (e.g. ArrayHelper). When you use a helper,
you should only use the concrete version and never use the base
class.
16.1.1 Core Helper Classes
The following core helper classes are provided in the Yii releases:
• ArrayHelper
• Console
• FileHelper
• Html
455
456 CHAPTER 16. HELPERS
• HtmlPurifier
• Image
• Inflector
• Json
• Markdown
• Security
• StringHelper
• Url
• VarDumper
16.1.2 Extending Helper Classes
To custom a core helper class (e.g. yiihelpersArrayHelper), you should
extend from its corresponding base class (e.g. yiihelpersBaseArrayHelper
) and name your class the same as the corresponding concrete class (e.g.
yiihelpersArrayHelper), including its namespace.
The following example shows how to customize the yiihelpersArrayHelper
::merge() method of the yiihelpersArrayHelper class:
namespace yiihelpers;
use yiihelpersBaseArrayHelper;
class ArrayHelper extends BaseArrayHelper
{
public static function merge($a, $b)
{
// your custom implementation
}
}
Save your class in a file named ArrayHelper.php. The file can be in any
directory, such as @app/components.
Next, in your application’s entry script, add the following line of code
after including the yii.php file:
Yii::$classMap[’yiihelpersArrayHelper’] = ’path/to/ArrayHelper.php’;
The above line instructs the Yii class autoloader to load your version of the
helper class, instead of the one included in the Yii releases.
16.1. HELPERS 457
Error: not existing file: helper-array.md
458 CHAPTER 16. HELPERS
Error: not existing file: helper-html.md
16.1. HELPERS 459
Error: not existing file: helper-url.md
460 CHAPTER 16. HELPERS
Error: not existing file: helper-security.md

More Related Content

PDF
Java web programming
PDF
BOOK - IBM Implementing ibm system directory 6.1
PDF
Report-V1.5_with_comments
PDF
Open erp openobject-developer
PDF
Openobject developer1
PDF
Openobject developer (2)
PDF
Flask docs
PDF
Enabling mobile apps with ibm worklight application center red
Java web programming
BOOK - IBM Implementing ibm system directory 6.1
Report-V1.5_with_comments
Open erp openobject-developer
Openobject developer1
Openobject developer (2)
Flask docs
Enabling mobile apps with ibm worklight application center red

What's hot (18)

PDF
IBM Streams - Redbook
PDF
Certification study guide ibm tivoli access manager for e business 6.0 sg247202
PDF
Managing an soa environment with tivoli redp4318
DOC
Moss2007
PDF
Selenium python
PDF
Akka java
PDF
Cognos v10.1
PDF
Yii blog-1.1.9
PDF
html-css-bootstrap-javascript-and-jquery
PDF
Businessobjects access analysis
PDF
Open edX Building and Running a Course
PDF
Maintenance planner
PDF
Akeeba backup-guide
PDF
Introducing tivoli personalized services manager 1.1 sg246031
PDF
MySQL Query Browser
PDF
Php myadmin documentation
PDF
Patterns: Implementing an SOA using an enterprise service bus (ESB)
PDF
IBM enterprise Content Management
IBM Streams - Redbook
Certification study guide ibm tivoli access manager for e business 6.0 sg247202
Managing an soa environment with tivoli redp4318
Moss2007
Selenium python
Akka java
Cognos v10.1
Yii blog-1.1.9
html-css-bootstrap-javascript-and-jquery
Businessobjects access analysis
Open edX Building and Running a Course
Maintenance planner
Akeeba backup-guide
Introducing tivoli personalized services manager 1.1 sg246031
MySQL Query Browser
Php myadmin documentation
Patterns: Implementing an SOA using an enterprise service bus (ESB)
IBM enterprise Content Management
Ad

Viewers also liked (10)

PPTX
Yii2
PDF
Membuat Aplikasi Kesiswaan Menggunakan Yii Framework Bagian 3
DOCX
Membangun aplikasi berbasis web dengan menggunakan framework
PDF
Membuat Aplikasi Kesiswaan Menggunakan Yii Framework - Bagian 2
ODP
Membuat Aplikasi Kesiswaan Menggunakan Yii Framework - Bagian 1
PDF
Get things done with Yii - quickly build webapplications
PPTX
Yii Training session-1
PDF
Filosofi belajar abad 21
PPTX
A site in 15 minutes with yii
PPTX
yii framework
Yii2
Membuat Aplikasi Kesiswaan Menggunakan Yii Framework Bagian 3
Membangun aplikasi berbasis web dengan menggunakan framework
Membuat Aplikasi Kesiswaan Menggunakan Yii Framework - Bagian 2
Membuat Aplikasi Kesiswaan Menggunakan Yii Framework - Bagian 1
Get things done with Yii - quickly build webapplications
Yii Training session-1
Filosofi belajar abad 21
A site in 15 minutes with yii
yii framework
Ad

Similar to Yii2 guide (20)

PDF
Sg247692 Websphere Accounting Chargeback For Tuam Guide
PDF
DBMS_Lab_Manual_&_Solution
PDF
Certification guide series ibm tivoli usage and accounting manager v7.1 imple...
PDF
java web_programming
PDF
Deployment guide series ibm tivoli composite application manager for web reso...
PDF
Deployment guide series ibm tivoli composite application manager for web reso...
PDF
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
PDF
Report on e-Notice App (An Android Application)
PDF
Mysql To Db2 Conversion Guide Ibm Redbooks
PDF
bkremer-report-final
PDF
Swi prolog-6.2.6
PDF
Solution deployment guide for ibm tivoli composite application manager for we...
PDF
Ibm mobile first strategy software approach
PDF
Automatic Detection of Performance Design and Deployment Antipatterns in Comp...
PDF
Deployment guide series ibm total storage productivity center for data sg247140
PDF
Ibm Informix Integration Through Data Federation Ibm Redbooks
PDF
eclipse.pdf
PDF
Certification guide series ibm tivoli netcool omn ibus v7.2 implementation sg...
PDF
BOOK - IBM tivoli netcool service quality manager data mediation gateway deve...
PDF
AI security book attack and defense. MLOPS
Sg247692 Websphere Accounting Chargeback For Tuam Guide
DBMS_Lab_Manual_&_Solution
Certification guide series ibm tivoli usage and accounting manager v7.1 imple...
java web_programming
Deployment guide series ibm tivoli composite application manager for web reso...
Deployment guide series ibm tivoli composite application manager for web reso...
Ibm tivoli intelligent think dynamic orchestrator pre proof of-concept cookbo...
Report on e-Notice App (An Android Application)
Mysql To Db2 Conversion Guide Ibm Redbooks
bkremer-report-final
Swi prolog-6.2.6
Solution deployment guide for ibm tivoli composite application manager for we...
Ibm mobile first strategy software approach
Automatic Detection of Performance Design and Deployment Antipatterns in Comp...
Deployment guide series ibm total storage productivity center for data sg247140
Ibm Informix Integration Through Data Federation Ibm Redbooks
eclipse.pdf
Certification guide series ibm tivoli netcool omn ibus v7.2 implementation sg...
BOOK - IBM tivoli netcool service quality manager data mediation gateway deve...
AI security book attack and defense. MLOPS

Recently uploaded (20)

PPTX
TRAVEL SUPPLIER API INTEGRATION | XML BOOKING ENGINE
PDF
How to Set Realistic Project Milestones and Deadlines
PDF
Software Development Company - swapdigit | Best Mobile App Development In India
PDF
Mobile App for Guard Tour and Reporting.pdf
PDF
IObit Driver Booster Pro Crack Latest Version Download
PPTX
SAP Business AI_L1 Overview_EXTERNAL.pptx
PPTX
Independent Consultants’ Biggest Challenges in ERP Projects – and How Apagen ...
PDF
MaterialX Virtual Town Hall - August 2025
PDF
WhatsApp Chatbots The Key to Scalable Customer Support.pdf
PDF
DOWNLOAD—IOBit Uninstaller Pro Crack Download Free
PDF
OpenImageIO Virtual Town Hall - August 2025
PPTX
MCP empowers AI Agents from Zero to Production
PPTX
Presentation - Summer Internship at Samatrix.io_template_2.pptx
PDF
Top AI Tools for Project Managers: My 2025 AI Stack
PDF
How to Write Automated Test Scripts Using Selenium.pdf
PDF
Difference Between Website and Web Application.pdf
PDF
10 Mistakes Agile Project Managers Still Make
PPTX
UNIT II: Software design, software .pptx
PDF
OpenTimelineIO Virtual Town Hall - August 2025
PPTX
Empowering Asian Contributions: The Rise of Regional User Groups in Open Sour...
TRAVEL SUPPLIER API INTEGRATION | XML BOOKING ENGINE
How to Set Realistic Project Milestones and Deadlines
Software Development Company - swapdigit | Best Mobile App Development In India
Mobile App for Guard Tour and Reporting.pdf
IObit Driver Booster Pro Crack Latest Version Download
SAP Business AI_L1 Overview_EXTERNAL.pptx
Independent Consultants’ Biggest Challenges in ERP Projects – and How Apagen ...
MaterialX Virtual Town Hall - August 2025
WhatsApp Chatbots The Key to Scalable Customer Support.pdf
DOWNLOAD—IOBit Uninstaller Pro Crack Download Free
OpenImageIO Virtual Town Hall - August 2025
MCP empowers AI Agents from Zero to Production
Presentation - Summer Internship at Samatrix.io_template_2.pptx
Top AI Tools for Project Managers: My 2025 AI Stack
How to Write Automated Test Scripts Using Selenium.pdf
Difference Between Website and Web Application.pdf
10 Mistakes Agile Project Managers Still Make
UNIT II: Software design, software .pptx
OpenTimelineIO Virtual Town Hall - August 2025
Empowering Asian Contributions: The Rise of Regional User Groups in Open Sour...

Yii2 guide

  • 1. The Definitive Guide to Yii 2.0 Qiang Xue, Alexander Makarov, Carsten Brandt, Klimov Paul and the Yii community Copyright 2014 Yii Software LLC.
  • 3. Contents 1 Introduction 1 1.1 What is Yii . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Upgrading from Version 1.1 . . . . . . . . . . . . . . . . . . . 2 2 Getting Started 15 2.1 Installing Yii . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2 Running Applications . . . . . . . . . . . . . . . . . . . . . . 20 2.3 Saying Hello . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.4 Working with Forms . . . . . . . . . . . . . . . . . . . . . . . 27 2.5 Working with Databases . . . . . . . . . . . . . . . . . . . . . 33 2.6 Generating Code with Gii . . . . . . . . . . . . . . . . . . . . 39 2.7 Looking Ahead . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3 Application Structure 47 3.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2 Entry Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.3 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.4 Application Components . . . . . . . . . . . . . . . . . . . . . 62 3.5 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 3.6 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 3.7 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 3.8 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 3.9 Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 3.10 Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 3.11 Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 3.12 Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 4 Handling Requests 143 4.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 4.2 Bootstrapping . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 4.3 Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 4.4 Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 4.5 Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 iii
  • 4. iv CONTENTS 4.6 URL Management . . . . . . . . . . . . . . . . . . . . . . . . 158 4.7 Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 165 4.8 Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 5 Key Concepts 171 5.1 Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 5.2 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 5.3 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 5.4 Behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 5.5 Configurations . . . . . . . . . . . . . . . . . . . . . . . . . . 187 5.6 Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 5.7 Class Autoloading . . . . . . . . . . . . . . . . . . . . . . . . 195 5.8 Service Locator . . . . . . . . . . . . . . . . . . . . . . . . . . 197 5.9 Dependency Injection Container . . . . . . . . . . . . . . . . . 199 6 Working with Databases 207 6.1 Database basics . . . . . . . . . . . . . . . . . . . . . . . . . . 207 6.2 Query Builder and Query . . . . . . . . . . . . . . . . . . . . 217 6.3 Active Record . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 6.4 Database Migration . . . . . . . . . . . . . . . . . . . . . . . . 246 7 Getting Data from Users 259 7.1 Working with Forms . . . . . . . . . . . . . . . . . . . . . . . 259 7.2 Validating Input . . . . . . . . . . . . . . . . . . . . . . . . . 263 7.3 Uploading Files . . . . . . . . . . . . . . . . . . . . . . . . . . 276 8 Displaying Data 283 8.1 Data Formatter . . . . . . . . . . . . . . . . . . . . . . . . . . 283 8.2 Data providers . . . . . . . . . . . . . . . . . . . . . . . . . . 289 8.3 Data widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . 291 8.4 Working with Client Scripts . . . . . . . . . . . . . . . . . . . 301 8.5 Theming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 9 Security 307 9.1 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 307 9.2 Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 9.3 Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 10 Caching 329 10.1 Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 10.2 Data Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 10.3 Fragment Caching . . . . . . . . . . . . . . . . . . . . . . . . 337 10.4 Page Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 10.5 HTTP Caching . . . . . . . . . . . . . . . . . . . . . . . . . . 341
  • 5. CONTENTS v 11 RESTful Web Services 345 11.1 Quick Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345 11.2 Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 11.3 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 11.4 Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 11.5 Response Formatting . . . . . . . . . . . . . . . . . . . . . . . 358 11.6 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . 361 11.7 Rate Limiting . . . . . . . . . . . . . . . . . . . . . . . . . . . 364 11.8 Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 11.9 Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 368 12 Development Tools 371 12.1 Debug toolbar and debugger . . . . . . . . . . . . . . . . . . . 371 12.2 The Gii code generation tool . . . . . . . . . . . . . . . . . . 375 13 Testing 383 13.1 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 13.2 Testing environment setup . . . . . . . . . . . . . . . . . . . . 385 13.3 Unit Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 13.4 Functional Tests . . . . . . . . . . . . . . . . . . . . . . . . . 386 13.5 Acceptance Tests . . . . . . . . . . . . . . . . . . . . . . . . . 386 13.6 Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 13.7 Managing Fixtures . . . . . . . . . . . . . . . . . . . . . . . . 392 14 Special Topics 395 14.1 Advanced application template . . . . . . . . . . . . . . . . . 395 14.2 Creating your own Application structure . . . . . . . . . . . . 401 14.3 Console applications . . . . . . . . . . . . . . . . . . . . . . . 402 14.4 Core Validators . . . . . . . . . . . . . . . . . . . . . . . . . . 406 14.5 Internationalization . . . . . . . . . . . . . . . . . . . . . . . . 417 14.6 Mailing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428 14.7 Performance Tuning . . . . . . . . . . . . . . . . . . . . . . . 432 14.8 Using template engines . . . . . . . . . . . . . . . . . . . . . . 439 14.9 Working with Third-Party Code . . . . . . . . . . . . . . . . . 446 15 Widgets 451 15.1 Bootstrap Widgets . . . . . . . . . . . . . . . . . . . . . . . . 451 15.2 Jquery UI Widgets . . . . . . . . . . . . . . . . . . . . . . . . 453 16 Helpers 455 16.1 Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
  • 7. Chapter 1 Introduction 1.1 What is Yii Yii is a high performance, component-based PHP framework for rapidly developing modern Web applications. The name Yii (pronounced Yee or [ji :]) means “simple and evolutionary” in Chinese. It can also be thought of as an acronym for Yes It Is! 1.1.1 What is Yii Best for? Yii is a generic Web programming framework, meaning that it can be used for developing all kinds of Web applications using PHP. Because of its component-based architecture and sophisticated caching support, it is es- pecially suitable for developing large-scale applications such as portals, for- ums, content management systems (CMS), e-commerce projects, RESTful Web services, and so on. 1.1.2 How does Yii Compare with Other Frameworks? If you’re already familiar with another framework, you may appreciate know- ing how Yii compares: • Like most PHP frameworks, Yii implements the MVC (Model-View- Controller) design pattern and promotes code organization based on that pattern. • Yii takes the philosophy that code should be written in a simple yet elegant way. Yii will never try to over-design things mainly for the purpose of strictly following some design pattern. • Yii is a full-stack framework providing many proven and ready-to- use features: query builders and ActiveRecord for both relational and 1
  • 8. 2 CHAPTER 1. INTRODUCTION NoSQL databases; RESTful API development support; multi-tier cach- ing support; and more. • Yii is extremely extensible. You can customize or replace nearly every piece of the core’s code. You can also take advantage of Yii’s solid extension architecture to use or develop redistributable extensions. • High performance is always a primary goal of Yii. Yii is not a one-man show, it is backed up by a strong core developer team1, as well as a large community of professionals constantly contributing to Yii’s development. The Yii developer team keeps a close eye on the latest Web development trends and on the best practices and features found in other frameworks and projects. The most relevant best practices and features found elsewhere are regularly incorporated into the core framework and ex- posed via simple and elegant interfaces. 1.1.3 Yii Versions Yii currently has two major versions available: 1.1 and 2.0. Version 1.1 is the old generation and is now in maintenance mode. Version 2.0 is a com- plete rewrite of Yii, adopting the latest technologies and protocols, including Composer, PSR, namespaces, traits, and so forth. Version 2.0 represents the current generation of the framework and will receive the main development efforts over the next few years. This guide is mainly about version 2.0. 1.1.4 Requirements and Prerequisites Yii 2.0 requires PHP 5.4.0 or above. You can find more detailed requirements for individual features by running the requirement checker included in every Yii release. Using Yii requires basic knowledge of object-oriented programming (OOP), as Yii is a pure OOP-based framework. Yii 2.0 also makes use of the latest features of PHP, such as namespaces2 and traits3. Understanding these con- cepts will help you more easily pick up Yii 2.0. 1.2 Upgrading from Version 1.1 There are many differences between versions 1.1 and 2.0 of Yii as the frame- work was completely rewritten for 2.0. As a result, upgrading from version 1.1 is not as trivial as upgrading between minor versions. In this guide you’ll find the major differences between the two versions. 1 http://www.yiiframework.com/about/ 2 http://www.php.net/manual/en/language.namespaces.php 3 http://www.php.net/manual/en/language.oop5.traits.php
  • 9. 1.2. UPGRADING FROM VERSION 1.1 3 If you have not used Yii 1.1 before, you can safely skip this section and turn directly to “Getting started“. Please note that Yii 2.0 introduces more new features than are covered in this summary. It is highly recommended that you read through the whole definitive guide to learn about them all. Chances are that some features you previously had to develop for yourself are now part of the core code. 1.2.1 Installation Yii 2.0 fully embraces Composer4, the de facto PHP package manager. In- stallation of the core framework, as well as extensions, are handled through Composer. Please refer to the Installing Yii section to learn how to install Yii 2.0. If you want to create new extensions, or turn your existing 1.1 exten- sions into 2.0-compatible extensions, please refer to the Creating Extensions section of the guide. 1.2.2 PHP Requirements Yii 2.0 requires PHP 5.4 or above, which is a huge improvement over PHP version 5.2 that is required by Yii 1.1. As a result, there are many differences on the language level that you should pay attention to. Below is a summary of the major changes regarding PHP: • Namespaces5. • Anonymous functions6. • Short array syntax [...elements...] is used instead of array(...elements ...). • Short echo tags <?= are used in view files. This is safe to use starting from PHP 5.4. • SPL classes and interfaces7. • Late Static Bindings8. • Date and Time9. • Traits10. 4 https://getcomposer.org/ 5 http://php.net/manual/en/language.namespaces.php 6 http://php.net/manual/en/functions.anonymous.php 7 http://php.net/manual/en/book.spl.php 8 http://php.net/manual/en/language.oop5.late-static-bindings.php 9 http://php.net/manual/en/book.datetime.php 10 http://php.net/manual/en/language.oop5.traits.php
  • 10. 4 CHAPTER 1. INTRODUCTION • intl11. Yii 2.0 makes use of the intl PHP extension to support inter- nationalization features. 1.2.3 Namespace The most obvious change in Yii 2.0 is the use of namespaces. Almost every core class is namespaced, e.g., yiiwebRequest. The “C” prefix is no longer used in class names. The naming scheme now follows the directory structure. For example, yiiwebRequest indicates that the corresponding class file is web /Request.php under the Yii framework folder. (You can use any core class without explicitly including that class file, thanks to the Yii class loader.) 1.2.4 Component and Object Yii 2.0 breaks the CComponent class in 1.1 into two classes: yiibaseObject and yiibaseComponent. The yiibaseObject class is a lightweight base class that allows defining object properties via getters and setters. The yii baseComponent class extends from yiibaseObject and supports events and behaviors. If your class does not need the event or behavior feature, you should consider using yiibaseObject as the base class. This is usually the case for classes that represent basic data structures. 1.2.5 Object Configuration The yiibaseObject class introduces a uniform way of configuring objects. Any descendant class of yiibaseObject should declare its constructor (if needed) in the following way so that it can be properly configured: class MyClass extends yiibaseObject { public function __construct($param1, $param2, $config = []) { // ... initialization before configuration is applied parent::__construct($config); } public function init() { parent::init(); // ... initialization after configuration is applied } } 11 http://php.net/manual/en/book.intl.php
  • 11. 1.2. UPGRADING FROM VERSION 1.1 5 In the above, the last parameter of the constructor must take a configuration array that contains name-value pairs for initializing the properties at the end of the constructor. You can override the yiibaseObject::init() method to do initialization work that should be done after the configuration has been applied. By following this convention, you will be able to create and configure new objects using a configuration array: $object = Yii::createObject([ ’class’ => ’MyClass’, ’property1’ => ’abc’, ’property2’ => ’cde’, ], [$param1, $param2]); More details about configurations can be found in the Object Configurations section. 1.2.6 Events In Yii 1, events were created by defining an on-method (e.g., onBeforeSave). In Yii 2, you can now use any event name. You trigger an event by calling the yiibaseComponent::trigger() method: $event = new yiibaseEvent; $component->trigger($eventName, $event); To attach a handler to an event, use the yiibaseComponent::on() method: $component->on($eventName, $handler); // To detach the handler, use: // $component->off($eventName, $handler); There are many enhancements to the event features. For more details, please refer to the Events section. 1.2.7 Path Aliases Yii 2.0 expands the usage of path aliases to both file/directory paths and URLs. Yii 2.0 also now requires an alias name to start with the @ character, to differentiate aliases from normal file/directory paths or URLs. For ex- ample, the alias @yii refers to the Yii installation directory. Path aliases are supported in most places in the Yii core code. For example, yiicaching FileCache::cachePath can take both a path alias and a normal directory path. A path alias is also closely related to a class namespace. It is recommen- ded that a path alias be defined for each root namespace, thereby allowing you to use Yii the class autoloader without any further configuration. For example, because @yii refers to the Yii installation directory, a class like yiiwebRequest can be autoloaded. If you use a third party library, such as the Zend Framework, you may define a path alias @Zend that refers to that
  • 12. 6 CHAPTER 1. INTRODUCTION framework’s installation directory. Once you’ve done that, Yii will be able to autoload any class in that Zend Framework library, too. More on path aliases can be found in the Path Aliases section. 1.2.8 Views The most significant change about views in Yii 2 is that the special variable $this in a view no longer refers to the current controller or widget. Instead, $this now refers to a view object, a new concept introduced in 2.0. The view object is of type yiiwebView, which represents the view part of the MVC pattern. If you want to access the controller or widget in a view, you can use $this->context. To render a partial view within another view, you use $this->render(), not $this->renderPartial(). The call to render also now has to be explicitly echoed, as the render() method returns the rendering result, rather than directly displaying it. For example: echo $this->render(’_item’, [’item’ => $item]); Besides using PHP as the primary template language, Yii 2.0 is also equipped with official support for two popular template engines: Smarty and Twig. The Prado template engine is no longer supported. To use these template engines, you need to configure the view application component by setting the yiibaseView::$renderers property. Please refer to the Template Engines section for more details. 1.2.9 Models Yii 2.0 uses yiibaseModel as the base model, similar to CModel in 1.1. The class CFormModel has been dropped entirely. Instead, in Yii 2 you should extend yiibaseModel to create a form model class. Yii 2.0 introduces a new method called yiibaseModel::scenarios() to declare supported scenarios, and to indicate under which scenario an attribute needs to be validated, can be considered as safe or not, etc. For example: public function scenarios() { return [ ’backend’ => [’email’, ’role’], ’frontend’ => [’email’, ’!role’], ]; } In the above, two scenarios are declared: backend and frontend. For the backend scenario, both the email and role attributes are safe, and can be massively assigned. For the frontend scenario, email can be massively assigned while role cannot. Both email and role should be validated using rules.
  • 13. 1.2. UPGRADING FROM VERSION 1.1 7 The yiibaseModel::rules() method is still used to declare the val- idation rules. Note that due to the introduction of yiibaseModel:: scenarios(), there is no longer an unsafe validator. In most cases, you do not need to override yiibaseModel::scenarios() if the yiibaseModel::rules() method fully specifies the scenarios that will exist, and if there is no need to declare unsafe attributes. To learn more details about models, please refer to the Models section. 1.2.10 Controllers Yii 2.0 uses yiiwebController as the base controller class, similar to CWebController in Yii 1.1. yiibaseAction is the base class for action classes. The most obvious impact of these changes on your code is that a con- troller action should return the content that you want to render instead of echoing it: public function actionView($id) { $model = appmodelsPost::findOne($id); if ($model) { return $this->render(’view’, [’model’ => $model]); } else { throw new yiiwebNotFoundHttpException; } } Please refer to the Controllers section for more details about controllers. 1.2.11 Widgets Yii 2.0 uses yiibaseWidget as the base widget class, similar to CWidget in Yii 1.1. To get better support for the framework in IDEs, Yii 2.0 introduces a new syntax for using widgets. The static methods yiibaseWidget::begin(), yiibaseWidget::end(), and yiibaseWidget::widget() have been in- troduced, to be used like so: use yiiwidgetsMenu; use yiiwidgetsActiveForm; // Note that you have to "echo" the result to display it echo Menu::widget([’items’ => $items]); // Passing an array to initialize the object properties $form = ActiveForm::begin([ ’options’ => [’class’ => ’form-horizontal’], ’fieldConfig’ => [’inputOptions’ => [’class’ => ’input-xlarge’]], ]); ... form input fields here ...
  • 14. 8 CHAPTER 1. INTRODUCTION ActiveForm::end(); Please refer to the Widgets section for more details. 1.2.12 Themes Themes work completely differently in 2.0. They are now based on a path mapping mechanism that maps a source view file path to a themed view file path. For example, if the path map for a theme is [’/web/views’ => ’/ web/themes/basic’], then the themed version for the view file /web/views/site /index.php will be /web/themes/basic/site/index.php. For this reason, themes can now be applied to any view file, even a view rendered outside of the context of a controller or a widget. Also, there is no more CThemeManager component. Instead, theme is a con- figurable property of the view application component. Please refer to the Theming section for more details. 1.2.13 Console Applications Console applications are now organized as controllers, like Web applications. Console controllers should extend from yiiconsoleController, similar to CConsoleCommand in 1.1. To run a console command, use yii <route>, where <route> stands for a controller route (e.g. sitemap/index). Additional anonymous arguments are passed as the parameters to the corresponding controller action method, while named arguments are parsed according to the declarations in yii consoleController::options(). Yii 2.0 supports automatic generation of command help information from comment blocks. Please refer to the Console Commands section for more details. 1.2.14 I18N Yii 2.0 removes the built-in date formatter and number formatter pieces in favor of the PECL intl PHP module12. Message translation is now performed via the i18n application compon- ent. This component manages a set of message sources, which allows you to use different message sources based on message categories. Please refer to the Internationalization section for more details. 1.2.15 Action Filters Action filters are implemented via behaviors now. To define a new, custom filter, extend from yiibaseActionFilter. To use a filter, attach the filter 12 http://pecl.php.net/package/intl
  • 15. 1.2. UPGRADING FROM VERSION 1.1 9 class to the controller as a behavior. For example, to use the yiifilters AccessControl filter, you would have the following code in a controller: public function behaviors() { return [ ’access’ => [ ’class’ => ’yiifiltersAccessControl’, ’rules’ => [ [’allow’ => true, ’actions’ => [’admin’], ’roles’ => [’@’]], ], ], ]; } Please refer to the Filtering section for more details. 1.2.16 Assets Yii 2.0 introduces a new concept called asset bundle that replaces the script package concept found in Yii 1.1. An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.) within a directory. Each asset bundle is represented as a class extending yiiwebAssetBundle. By registering an asset bundle via yiiwebAssetBundle::register(), you make the assets in that bundle accessible via the Web. Unlike in Yii 1, the page registering the bundle will automatically contain the references to the JavaScript and CSS files specified in that bundle. Please refer to the Managing Assets section for more details. 1.2.17 Helpers Yii 2.0 introduces many commonly used static helper classes, including. • yiihelpersHtml • yiihelpersArrayHelper • yiihelpersStringHelper • yiihelpersFileHelper • yiihelpersJson Please refer to the Helper Overview section for more details.
  • 16. 10 CHAPTER 1. INTRODUCTION 1.2.18 Forms Yii 2.0 introduces the field concept for building a form using yiiwidgets ActiveForm. A field is a container consisting of a label, an input, an er- ror message, and/or a hint text. A field is represented as an yiiwidgets ActiveField object. Using fields, you can build a form more cleanly than before: <?php $form = yiiwidgetsActiveForm::begin(); ?> <?= $form->field($model, ’username’) ?> <?= $form->field($model, ’password’)->passwordInput() ?> <div class="form-group"> <?= Html::submitButton(’Login’) ?> </div> <?php yiiwidgetsActiveForm::end(); ?> Please refer to the Creating Forms section for more details. 1.2.19 Query Builder In 1.1, query building was scattered among several classes, including CDbCommand , CDbCriteria, and CDbCommandBuilder. Yii 2.0 represents a DB query in terms of a yiidbQuery object that can be turned into a SQL statement with the help of yiidbQueryBuilder behind the scene. For example: $query = new yiidbQuery(); $query->select(’id, name’) ->from(’user’) ->limit(10); $command = $query->createCommand(); $sql = $command->sql; $rows = $command->queryAll(); Best of all, such query building methods can also be used when working with Active Record. Please refer to the Query Builder section for more details. 1.2.20 Active Record Yii 2.0 introduces a lot of changes to Active Record. The two most obvious ones involve query building and relational query handling. The CDbCriteria class in 1.1 is replaced by yiidbActiveQuery in Yii 2. That class extends from yiidbQuery, and thus inherits all query building methods. You call yiidbActiveRecord::find() to start building a query: // To retrieve all *active* customers and order them by their ID: $customers = Customer::find() ->where([’status’ => $active]) ->orderBy(’id’) ->all();
  • 17. 1.2. UPGRADING FROM VERSION 1.1 11 To declare a relation, simply define a getter method that returns an yiidb ActiveQuery object. The property name defined by the getter represents the relation name. For example, the following code declares an orders relation (in 1.1, you would have to declare relations in a central place relations()): class Customer extends yiidbActiveRecord { public function getOrders() { return $this->hasMany(’Order’, [’customer_id’ => ’id’]); } } Now you can use $customer->orders to access a customer’s orders from the related table. You can also use the following code to perform an on-the-fly relational query with a customized query condition: $orders = $customer->getOrders()->andWhere(’status=1’)->all(); When eager loading a relation, Yii 2.0 does it differently from 1.1. In partic- ular, in 1.1 a JOIN query would be created to select both the primary and the relational records. In Yii 2.0, two SQL statements are executed without using JOIN: the first statement brings back the primary records and the second brings back the relational records by filtering with the primary keys of the primary records. Instead of returning yiidbActiveRecord objects, you may chain the yiidbActiveQuery::asArray() method when building a query to return a large number of records. This will cause the query result to be returned as arrays, which can significantly reduce the needed CPU time and memory if large number of records . For example: $customers = Customer::find()->asArray()->all(); Another change is that you can’t define attribute default values through public properties anymore. If you need those, you should set them in the init method of your record class. public function init() { parent::init(); $this->status = self::STATUS_NEW; } There were some problems with overriding the constructor of an ActiveRecord class in 1.1. These are not present in version 2.0 anymore. Note that when adding parameters to the constructor you might have to override yiidb ActiveRecord::instantiate(). There are many other changes and enhancements to Active Record. Please refer to the Active Record section for more details.
  • 18. 12 CHAPTER 1. INTRODUCTION 1.2.21 Active Record Behaviors In 2.0, we have dropped the base behavior class CActiveRecordBehavior. If you want to create an Active Record Behavior, you will have to extend directly from yiibaseBehavior. If the behavior class needs to respond to some events of the owner, you have to override the events() method like the following, namespace appcomponents; use yiidbActiveRecord; use yiibaseBehavior; class MyBehavior extends Behavior { // ... public function events() { return [ ActiveRecord::EVENT_BEFORE_VALIDATE => ’beforeValidate’, ]; } public function beforeValidate($event) { // ... } } 1.2.22 User and IdentityInterface The CWebUser class in 1.1 is now replaced by yiiwebUser, and there is no more CUserIdentity class. Instead, you should implement the yiiweb IdentityInterface which is much more straightforward to use. The ad- vanced application template provides such an example. Please refer to the Authentication, Authorization, and Advanced Applic- ation Technique sections for more details. 1.2.23 URL Management URL management in Yii 2 is similar to that in 1.1. A major enhancement is that URL management now supports optional parameters. For example, if you have a rule declared as follows, then it will match both post/popular and post/1/popular. In 1.1, you would have had to use two rules to achieve the same goal. [ ’pattern’ => ’post/<page:d+>/<tag>’, ’route’ => ’post/index’, ’defaults’ => [’page’ => 1], ]
  • 19. 1.2. UPGRADING FROM VERSION 1.1 13 Please refer to the Url manager docs section for more details. 1.2.24 Using Yii 1.1 and 2.x together If you have legacy Yii 1.1 code that you want to use together with Yii 2.0, please refer to the Using Yii 1.1 and 2.0 Together section.
  • 20. 14 CHAPTER 1. INTRODUCTION
  • 21. Chapter 2 Getting Started 2.1 Installing Yii You can install Yii in two ways, using Composer1 or by downloading an archive file. The former is the preferred way, as it allows you to install new extensions or update Yii by simply running a single command. Note: Unlike with Yii 1, standard installations of Yii 2 result in both, the framework and an application skeleton being down- loaded and installed. 2.1.1 Installing via Composer If you do not already have Composer installed, you may do so by following the instructions at getcomposer.org2. On Linux and Mac OS X, you’ll run the following commands: curl -s http://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer On Windows, you’ll download and run Composer-Setup.exe3. Please refer to the Composer Documentation4 if you encounter any prob- lems or want to learn more about Composer usage. With Composer installed, you can install Yii by running the following commands under a Web-accessible folder: composer global require "fxp/composer-asset-plugin:1.0.0-beta2" composer create-project --prefer-dist yiisoft/yii2-app-basic basic The first command installs the composer asset plugin5 which allows man- aging bower and npm package dependencies through Composer. You only 1 http://getcomposer.org/ 2 https://getcomposer.org/download/ 3 https://getcomposer.org/Composer-Setup.exe 4 https://getcomposer.org/doc/ 5 https://github.com/francoispluchino/composer-asset-plugin/ 15
  • 22. 16 CHAPTER 2. GETTING STARTED need to run this command once for all. The second command installs Yii in a directory named basic. Tip: If you want to install the latest development version of Yii, you may use the following command instead, which adds a stability option6: composer create-project --prefer-dist --stability=dev yiisoft/ yii2-app-basic basic Note that the development version of Yii should not be used for production as it may break your running code. 2.1.2 Installing from an Archive File Installing Yii from an archive file involves three steps: 1. Download the archive file from yiiframework.com7. 2. Unpack the downloaded file to a Web-accessible folder. 3. Modify the config/web.php file by entering a secret key for the cookieValidationKey configuration item (this is done automatically if you are installing Yii using Composer): // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation ’cookieValidationKey’ => ’enter your secret key here’, 2.1.3 Other Installation Options The above installation instructions show how to install Yii, which also creates a basic Web application that works out of the box. This approach is a good starting point for small projects, or for when you just start learning Yii. But there are other installation options available: • If you only want to install the core framework and would like to build an entire application from scratch, you may follow the instructions as explained in Building Application from Scratch. • If you want to start with a more sophisticated application, better suited to team development environments, you may consider installing the Advanced Application Template. 6 https://getcomposer.org/doc/04-schema.md#minimum-stability 7 https://github.com/yiisoft/yii2/releases/download/2.0.0-rc/ yii-basic-app-2.0.0-rc.tgz
  • 23. 2.1. INSTALLING YII 17 2.1.4 Verifying the Installation After installation, you can use your browser to access the installed Yii ap- plication with the following URL: http://localhost/basic/web/index.php This URL assumes you have installed Yii in a directory named basic, directly under the Web server’s document root directory, and that the Web server is running on your local machine (localhost). You may need to adjust it to your installation environment. You should see the above “Congratulations!“ page in your browser. If not, please check if your PHP installation satisfies Yii’s requirements. You can check if the minimum requirements are met using one of the following approaches: • Use a browser to access the URL http://localhost/basic/requirements. php • Run the following commands: cd basic php requirements.php You should configure your PHP installation so that it meets the minimum requirements of Yii. Most importantly, you should have PHP 5.4 or above. You should also install the PDO PHP Extension8 and a corresponding data- base driver (such as pdo_mysql for MySQL databases), if your application needs a database. 8 http://www.php.net/manual/en/pdo.installation.php
  • 24. 18 CHAPTER 2. GETTING STARTED 2.1.5 Configuring Web Servers Info: You may skip this subsection for now if you are just test driving Yii with no intention of deploying it to a production server. The application installed according to the above instructions should work out of box with either an Apache HTTP server9 or an Nginx HTTP server10, on Windows, Mac OS X, or Linux running PHP 5.4 or higher. Yii 2.0 is also compatible the facebooks HHVM11 however there are some edge cases where HHVM behaves different than native PHP so you have to take some extra care when using HHVM. On a production server, you may want to configure your Web server so that the application can be accessed via the URL http://www.example.com /index.php instead of http://www.example.com/basic/web/index.php. Such con- figuration requires pointing the document root of your Web server to the basic/web folder. You may also want to hide index.php from the URL, as described in the URL Parsing and Generation section. In this subsection, you’ll learn how to configure your Apache or Nginx server to achieve these goals. Info: By setting basic/web as the document root, you also prevent end users from accessing your private application code and sensit- ive data files that are stored in the sibling directories of basic/web. Denying access to those other folders is a security improvement. Info: If your application will run in a shared hosting environment where you do not have permission to modify its Web server con- figuration, you may still adjust the structure of your application for better security. Please refer to the Shared Hosting Environ- ment section for more details. Recommended Apache Configuration Use the following configuration in Apache’s httpd.conf file or within a virtual host configuration. Note that you should replace path/to/basic/web with the actual path for basic/web. # Set document root to be "basic/web" DocumentRoot "path/to/basic/web" <Directory "path/to/basic/web"> # use mod_rewrite for pretty URL support RewriteEngine on 9 http://httpd.apache.org/ 10 http://nginx.org/ 11 http://hhvm.com/
  • 25. 2.1. INSTALLING YII 19 # If a directory or a file exists, use the request directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # Otherwise forward the request to index.php RewriteRule . index.php # ...other settings... </Directory> Recommended Nginx Configuration You should have installed PHP as an FPM SAPI12 to use Nginx13. Use the following Nginx configuration, replacing path/to/basic/web with the actual path for basic/web and mysite.local with the actual hostname to serve. server { charset utf-8; client_max_body_size 128M; listen 80; ## listen for ipv4 #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 server_name mysite.local; root /path/to/basic/web; index index.php; access_log /path/to/basic/log/access.log main; error_log /path/to/basic/log/error.log; location / { # Redirect everything that isn’t a real file to index.php try_files $uri $uri/ /index.php?$args; } # uncomment to avoid processing of calls to non-existing static files by Yii #location ~ .(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { # try_files $uri =404; #} #error_page 404 /404.html; location ~ .php$ { include fastcgi.conf; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; } location ~ /.(ht|svn|git) { deny all; 12 http://php.net/install.fpm 13 http://wiki.nginx.org/
  • 26. 20 CHAPTER 2. GETTING STARTED } } When using this configuration, you should also set cgi.fix_pathinfo=0 in the php.ini file in order to avoid many unnecessary system stat() calls. Also note that when running an HTTPS server, you need to add fastcgi_param HTTPS on; so that Yii can properly detect if a connection is secure. 2.2 Running Applications After installing Yii, you have a working Yii application that can be ac- cessed via the URL http://hostname/basic/web/index.php or http://hostname/ index.php, depending upon your configuration. This section will introduce the application’s built-in functionality, how the code is organized, and how the application handles requests in general. Info: For simplicity, throughout this “Getting Started” tutorial, it’s assumed that you have set basic/web as the document root of your Web server, and configured, the URL for accessing your application to be http://hostname/index.php or something similar. For your needs, please adjust the URLs in our descriptions ac- cordingly. 2.2.1 Functionality The basic application installed contains four pages: • The homepage, displayed when you access the URL http://hostname/ index.php, • the “About” page, • the “Contact” page, which displays a contact form that allows end users to contact you via email, • and the “Login” page, which displays a login form that can be used to authenticate end users. Try logging in with “admin/admin”, and you will find the “Login” main menu item will change to “Logout”. These pages share a common header and footer. The header contains a main menu bar to allow navigation among different pages. You should also see a toolbar at the bottom of the browser window. This is a useful debugger tool provided by Yii to record and display a lot of debugging information, such as log messages, response statuses, the database queries run, and so on.
  • 27. 2.2. RUNNING APPLICATIONS 21 2.2.2 Application Structure The most important directories and files in your application are (assuming the application’s root directory is basic): basic/ application base path composer.json used by Composer, describes package information config/ contains application and other configurations console.php the console application configuration web.php the Web application configuration commands/ contains console command classes controllers/ contains controller classes models/ contains model classes runtime/ contains files generated by Yii during runtime, such as logs and cache files vendor/ contains the installed Composer packages, including the Yii framework itself views/ contains view files web/ application Web root, contains Web accessible files assets/ contains published asset files (javascript and css) by Yii index.php the entry (or bootstrap) script for the application yii the Yii console command execution script In general, the files in the application can be divided into two types: those under basic/web and those under other directories. The former can be directly accessed via HTTP (i.e., in a browser), while the latter can not and should not be. Yii implements the model-view-controller (MVC)14 design pattern, which is reflected in the above directory organization. The models directory con- tains all model classes, the views directory contains all view scripts, and the controllers directory contains all controller classes. The following diagram shows the static structure of an application. 14 http://wikipedia.org/wiki/Model-view-controller
  • 28. 22 CHAPTER 2. GETTING STARTED Each application has an entry script web/index.php which is the only Web accessible PHP script in the application. The entry script takes an incoming request and creates an application instance to handle it. The application resolves the request with the help of its components, and dispatches the request to the MVC elements. Widgets are used in the views to help build complex and dynamic user interface elements. 2.2.3 Request Lifecycle The following diagram shows how an application handles a request.
  • 29. 2.2. RUNNING APPLICATIONS 23 1. A user makes a request to the entry script web/index.php. 2. The entry script loads the application configuration and creates an application instance to handle the request. 3. The application resolves the requested route with the help of the re- quest application component. 4. The application creates a controller instance to handle the request. 5. The controller creates an action instance and performs the filters for the action. 6. If any filter fails, the action is cancelled. 7. If all filters pass, the action is executed. 8. The action loads a data model, possibly from a database. 9. The action renders a view, providing it with the data model. 10. The rendered result is returned to the response application component. 11. The response component sends the rendered result to the user’s browser.
  • 30. 24 CHAPTER 2. GETTING STARTED 2.3 Saying Hello This section describes how to create a new “Hello” page in your application. To achieve this goal, you will create an action and a view: • The application will dispatch the page request to the action • and the action will in turn render the view that shows the word “Hello” to the end user. Through this tutorial, you will learn three things: 1. How to create an action to respond to requests, 2. how to create a view to compose the response’s content, and 3. how an application dispatches requests to actions. 2.3.1 Creating an Action For the “Hello” task, you will create a say action that reads a message para- meter from the request and displays that message back to the user. If the request does not provide a message parameter, the action will display the default “Hello” message. Info: Actions are the objects that end users can directly refer to for execution. Actions are grouped by controllers. The execution result of an action is the response that an end user will receive. Actions must be declared in controllers. For simplicity, you may declare the say action in the existing SiteController. This controller is defined in the class file controllers/SiteController.php. Here is the start of the new action: <?php namespace appcontrollers; use yiiwebController; class SiteController extends Controller { // ...existing code... public function actionSay($message = ’Hello’) { return $this->render(’say’, [’message’ => $message]); } }
  • 31. 2.3. SAYING HELLO 25 In the above code, the say action is defined as a method named actionSay in the SiteController class. Yii uses the prefix action to differentiate action methods from non-action methods in a controller class. The name after the action prefix maps to the action’s ID. When it comes to naming your actions, you should understand how Yii treats action IDs. Action IDs are always referenced in lower case. If an action ID requires multiple words, they will be concatenated by dashes (e.g., create-comment). Action method names are mapped to action IDs by remov- ing any dashes from the IDs, capitalizing the first letter in each word, and prefixing the resulting with action. For example, the action ID create-comment corresponds to the action method name actionCreateComment. The action method in our example takes a parameter $message, whose value defaults to "Hello" (in exactly the same way you set a default value for any function or method argument in PHP). When the application receives a request and determines that the say action is responsible for handling said request, the application will populate this parameter with the same named parameter found in the request. In other words, if the request includes a message parameter with a value of "Goodbye", the $message variable within the action will be assigned that value. Within the action method, yiiwebController::render() is called to render a view file named say. The message parameter is also passed to the view so that it can be used there. The rendering result is returned by the action method. That result will be received by the application and displayed to the end user in the browser (as part of a complete HTML page). 2.3.2 Creating a View Views are scripts you write to generate a response’s content. For the “Hello” task, you will create a say view that prints the message parameter received from the action method, and passed by the action to the view: <?php use yiihelpersHtml; ?> <?= Html::encode($message) ?> The say view should be saved in the file views/site/say.php. When the method yiiwebController::render() is called in an action, it will look for a PHP file named as views/ControllerID/ViewName.php. Note that in the above code, the message parameter is yiihelpersHtml ::encode() before being printed. This is necessary as the parameter comes from an end user, making it vulnerable to cross-site scripting (XSS) attacks15 by embedding malicious JavaScript code in the parameter. Naturally, you may put more content in the say view. The content can consist of HTML tags, plain text, and even PHP statements. In fact, the say 15 http://en.wikipedia.org/wiki/Cross-site_scripting
  • 32. 26 CHAPTER 2. GETTING STARTED view is just a PHP script that is executed by the yiiwebController:: render() method. The content printed by the view script will be returned to the application as the response’s result. The application will in turn output this result to the end user. 2.3.3 Trying it Out After creating the action and the view, you may access the new page by accessing the following URL: http://hostname/index.php?r=site/say&message=Hello+World This URL will result in a page displaying “Hello World”. The page shares the same header and footer as the other application pages. If you omit the message parameter in the URL, you would see the page display just “Hello”. This is because message is passed as a parameter to the actionSay() method, and when it is omitted, the default value of "Hello" will be used instead. Info: The new page shares the same header and footer as other pages because the yiiwebController::render() method will automatically embed the result of the say view in a so-called layout which in this case is located at views/layouts/main.php. The r parameter in the above URL requires more explanation. It stands for route, an application wide unique ID that refers to an action. The route’s format is ControllerID/ActionID. When the application receives a request, it will check this parameter, using the ControllerID part to determine which
  • 33. 2.4. WORKING WITH FORMS 27 controller class should be instantiated to handle the request. Then, the controller will use the ActionID part to determine which action should be instantiated to do the real work. In this example case, the route site/say will be resolved to the SiteController controller class and the say action. As a result, the SiteController::actionSay() method will be called to handle the request. Info: Like actions, controllers also have IDs that uniquely identify them in an application. Controller IDs use the same naming rules as action IDs. Controller class names are derived from controller IDs by removing dashes from the IDs, capitalizing the first letter in each word, and suffixing the resulting string with the word Controller. For example, the controller ID post-comment corresponds to the controller class name PostCommentController. 2.3.4 Summary In this section, you have touched the controller and view parts of the MVC design pattern. You created an action as part of a controller to handle a specific request. And you also created a view to compose the response’s content. In this simple example, no model was involved as the only data used was the message parameter. You have also learned about routes in Yii, which act as the bridge between user requests and controller actions. In the next section, you will learn how to create a model, and add a new page containing an HTML form. 2.4 Working with Forms This section describes how to create a new page with a form for getting data from users. The page will display a form with a name input field and an email input field. After getting those two pieces of information from the user, the page will echo the entered values back for confirmation. To achieve this goal, besides creating an action and two views, you will also create a model. Through this tutorial, you will learn how to: • Create a model to represent the data entered by a user through a form • Declare rules to validate the data entered • Build an HTML form in a view
  • 34. 28 CHAPTER 2. GETTING STARTED 2.4.1 Creating a Model The data to be requested from the user will be represented by an EntryForm model class as shown below and saved in the file models/EntryForm.php. Please refer to the Class Autoloading section for more details about the class file naming convention. <?php namespace appmodels; use yiibaseModel; class EntryForm extends Model { public $name; public $email; public function rules() { return [ [[’name’, ’email’], ’required’], [’email’, ’email’], ]; } } The class extends from yiibaseModel, a base class provided by Yii, com- monly used to represent form data. Info: yiibaseModel is used as a parent for model classes not associated with database tables. yiidbActiveRecord is nor- mally the parent for model classes that do correspond to database tables. The EntryForm class contains two public members, name and email, which are used to store the data entered by the user. It also contains a method named rules(), which returns a set of rules for validating the data. The validation rules declared above state that • both the name and email values are required • the email data must be a syntactically valid email address If you have an EntryForm object populated with the data entered by a user, you may call its yiibaseModel::validate() to trigger the data validation routines. A data validation failure will set the yiibaseModel::hasErrors property to true, and you may learn what validation errors occurred through yiibaseModel::getErrors.
  • 35. 2.4. WORKING WITH FORMS 29 <?php $model = new EntryForm(); $model->name = ’Qiang’; $model->email = ’bad’; if ($model->validate()) { // Good! } else { // Failure! // Use $model->getErrors() } 2.4.2 Creating an Action Next, you’ll need to create an entry action in the site controller that will use the new model. The process of creating and using actions was explained in the Saying Hello section. <?php namespace appcontrollers; use Yii; use yiiwebController; use appmodelsEntryForm; class SiteController extends Controller { // ...existing code... public function actionEntry() { $model = new EntryForm; if ($model->load(Yii::$app->request->post()) && $model->validate()) { // valid data received in $model // do something meaningful here about $model ... return $this->render(’entry-confirm’, [’model’ => $model]); } else { // either the page is initially displayed or there is some validation error return $this->render(’entry’, [’model’ => $model]); } } } The action first creates an EntryForm object. It then tries to populate the model with the data from $_POST, provided in Yii by yiiwebRequest:: post(). If the model is successfully populated (i.e., if the user has submitted the HTML form), the action will call yiibaseModel::validate() to make
  • 36. 30 CHAPTER 2. GETTING STARTED sure the values entered are valid. Info: The expression Yii::$app represents the application instance, which is a globally accessible singleton. It is also a service loc- ator that provides components such as request, response, db, etc. to support specific functionality. In the above code, the request component of the application instance is used to access the $_POST data. If everything is fine, the action will render a view named entry-confirm to confirm the successful submission of the data to the user. If no data is sub- mitted or the data contains errors, the entry view will be rendered, wherein the HTML form will be shown, along with any validation error messages. Note: In this very simple example we just render the confirmation page upon valid data submission. In practice, you should con- sider using yiiwebController::refresh() or yiiwebController ::redirect() to avoid form resubmission problems16. 2.4.3 Creating Views Finally, create two view files named entry-confirm and entry. These will be rendered by the entry action, as just described. The entry-confirm view simply displays the name and email data. It should be stored in the file views/site/entry-confirm.php. <?php use yiihelpersHtml; ?> <p>You have entered the following information:</p> <ul> <li><label>Name</label>: <?= Html::encode($model->name) ?></li> <li><label>Email</label>: <?= Html::encode($model->email) ?></li> </ul> The entry view displays an HTML form. It should be stored in the file views/site/entry.php. <?php use yiihelpersHtml; use yiiwidgetsActiveForm; ?> <?php $form = ActiveForm::begin(); ?> <?= $form->field($model, ’name’) ?> <?= $form->field($model, ’email’) ?> 16 http://en.wikipedia.org/wiki/Post/Redirect/Get
  • 37. 2.4. WORKING WITH FORMS 31 <div class="form-group"> <?= Html::submitButton(’Submit’, [’class’ => ’btn btn-primary’]) ?> </div> <?php ActiveForm::end(); ?> The view uses a powerful widget called yiiwidgetsActiveForm to build the HTML form. The begin() and end() methods of the widget render the opening and closing form tags, respectively. Between the two method calls, input fields are created by the yiiwidgetsActiveForm::field() method. The first input field is for the “name” data, and the second for the “email” data. After the input fields, the yiihelpersHtml::submitButton() method is called to generate a submit button. 2.4.4 Trying it Out To see how it works, use your browser to access the following URL: http://hostname/index.php?r=site/entry You will see a page displaying a form with two input fields. In front of each input field, a label indicates what data is to be entered. If you click the submit button without entering anything, or if you do not provide a valid email address, you will see an error message displayed next to each problematic input field. After entering a valid name and email address and clicking the submit button, you will see a new page displaying the data that you just entered.
  • 38. 32 CHAPTER 2. GETTING STARTED Magic Explained You may wonder how the HTML form works behind the scene, because it seems almost magical that it can display a label for each input field and show error messages if you do not enter the data correctly without reloading the page. Yes, the data validation is initially done on the client side using JavaS- cript, and secondarily performed on the server side via PHP. yiiwidgets ActiveForm is smart enough to extract the validation rules that you have declared in EntryForm, turn them into executable JavaScript code, and use the JavaScript to perform data validation. In case you have disabled JavaS- cript on your browser, the validation will still be performed on the server side, as shown in the actionEntry() method. This ensures data validity in all circumstances. Warning: Client-side validation is a convenience that provides for a better user experience. Server-side validation is always re- quired, whether or not client-side validation is in place. The labels for input fields are generated by the field() method, using the property names from the model. For example, the label Name will be generated for the name property. You may customize a label within a view using the following code: <?= $form->field($model, ’name’)->label(’Your Name’) ?> <?= $form->field($model, ’email’)->label(’Your Email’) ?>
  • 39. 2.5. WORKING WITH DATABASES 33 Info: Yii provides many such widgets to help you quickly build complex and dynamic views. As you will learn later, writing a new widget is also extremely easy. You may want to turn much of your view code into reusable widgets to simplify view development in future. 2.4.5 Summary In this section of the guide, you have touched every part in the MVC design pattern. You have learned how to create a model class to represent the user data and validate said data. You have also learned how to get data from users and how to display data back in the browser. This is a task that could take you a lot of time when developing an application, but Yii provides powerful widgets to make this task very easy. In the next section, you will learn how to work with databases, which are needed in nearly every application. 2.5 Working with Databases This section will describe how to create a new page that displays country data fetched from a database table named country. To achieve this goal, you will configure a database connection, create an Active Record class, define an action, and create a view. Through this tutorial, you will learn how to: • Configure a DB connection • Define an Active Record class • Query data using the Active Record class • Display data in a view in a paginated fashion Note that in order to finish this section, you should have basic knowledge and experience using databases. In particular, you should know how to create a database, and how to execute SQL statements using a DB client tool. 2.5.1 Preparing the Database To begin, create a database named yii2basic, from which you will fetch data in your application. You may create an SQLite, MySQL, PostgreSQL, MSSQL or Oracle database, as Yii has built-in support for many database applications. For simplicity, MySQL will be assumed in the following de- scription.
  • 40. 34 CHAPTER 2. GETTING STARTED Next, create a table named country in the database, and insert some sample data. You may run the following SQL statements to do so: CREATE TABLE ‘country‘ ( ‘code‘ CHAR(2) NOT NULL PRIMARY KEY, ‘name‘ CHAR(52) NOT NULL, ‘population‘ INT(11) NOT NULL DEFAULT ’0’ ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO ‘country‘ VALUES (’AU’,’Australia’,18886000); INSERT INTO ‘country‘ VALUES (’BR’,’Brazil’,170115000); INSERT INTO ‘country‘ VALUES (’CA’,’Canada’,1147000); INSERT INTO ‘country‘ VALUES (’CN’,’China’,1277558000); INSERT INTO ‘country‘ VALUES (’DE’,’Germany’,82164700); INSERT INTO ‘country‘ VALUES (’FR’,’France’,59225700); INSERT INTO ‘country‘ VALUES (’GB’,’United Kingdom’,59623400); INSERT INTO ‘country‘ VALUES (’IN’,’India’,1013662000); INSERT INTO ‘country‘ VALUES (’RU’,’Russia’,146934000); INSERT INTO ‘country‘ VALUES (’US’,’United States’,278357000); At this point, you have a database named yii2basic, and within it a country table with three columns, containing ten rows of data. 2.5.2 Configuring a DB Connection Before proceeding, make sure you have installed both the PDO17 PHP ex- tension and the PDO driver for the database you are using (e.g. pdo_mysql for MySQL). This is a basic requirement if your application uses a relational database. With those installed, open the file config/db.php and change the para- meters to be correct for your database. By default, the file contains the following: <?php return [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=yii2basic’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ]; The config/db.php file is a typical file-based configuration tool. This particu- lar configuration file specifies the parameters needed to create and initialize a yiidbConnection instance through which you can make SQL queries against the underlying database. The DB connection configured above can be accessed in the application code via the expression Yii::$app->db. 17 http://www.php.net/manual/en/book.pdo.php
  • 41. 2.5. WORKING WITH DATABASES 35 Info: The config/db.php file will be included by the main applica- tion configuration config/web.php, which specifies how the applic- ation instance should be initialized. For more information, please refer to the Configurations section. 2.5.3 Creating an Active Record To represent and fetch the data in the country table, create an Active Record- derived class named Country, and save it in the file models/Country.php. <?php namespace appmodels; use yiidbActiveRecord; class Country extends ActiveRecord { } The Country class extends from yiidbActiveRecord. You do not need to write any code inside of it! With just the above code, Yii will guess the associated table name from the class name. Info: If no direct match can be made from the class name to the table name, you can override the yiidbActiveRecord:: tableName() method to explicitly specify the associated table name. Using the Country class, you can easily manipulate data in the country table, as shown in these snippets: use appmodelsCountry; // get all rows from the country table and order them by "name" $countries = Country::find()->orderBy(’name’)->all(); // get the row whose primary key is "US" $country = Country::findOne(’US’); // displays "United States" echo $country->name; // modifies the country name to be "U.S.A." and save it to database $country->name = ’U.S.A.’; $country->save(); Info: Active Record is a powerful way to access and manipulate database data in an object-oriented fashion. You may find more detailed information in the Active Record section. Alternatively, you may also interact with a database using a lower-level data accessing method called Data Access Objects.
  • 42. 36 CHAPTER 2. GETTING STARTED 2.5.4 Creating an Action To expose the country data to end users, you need to create a new action. Instead of placing the new action in the site controller, like you did in the previous sections, it makes more sense to create a new controller specific- ally for all actions related to the country data. Name this new controller CountryController, and create an index action in it, as shown in the following. <?php namespace appcontrollers; use yiiwebController; use yiidataPagination; use appmodelsCountry; class CountryController extends Controller { public function actionIndex() { $query = Country::find(); $pagination = new Pagination([ ’defaultPageSize’ => 5, ’totalCount’ => $query->count(), ]); $countries = $query->orderBy(’name’) ->offset($pagination->offset) ->limit($pagination->limit) ->all(); return $this->render(’index’, [ ’countries’ => $countries, ’pagination’ => $pagination, ]); } } Save the above code in the file controllers/CountryController.php. The index action calls Country::find(). This Active Record method builds a DB query and retrieves all of the data from the country table. To limit the number of countries returned in each request, the query is paginated with the help of a yiidataPagination object. The Pagination object serves two purposes: • Sets the offset and limit clauses for the SQL statement represented by the query so that it only returns a single page of data at a time (at most 5 rows in a page). • It’s used in the view to display a pager consisting of a list of page buttons, as will be explained in the next subsection.
  • 43. 2.5. WORKING WITH DATABASES 37 At the end of the code, the index action renders a view named index, and passes the country data as well as the pagination information to it. 2.5.5 Creating a View Under the views directory, first create a sub-directory named country. This folder will be used to hold all the views rendered by the country controller. Within the views/country directory, create a file named index.php containing the following: <?php use yiihelpersHtml; use yiiwidgetsLinkPager; ?> <h1>Countries</h1> <ul> <?php foreach ($countries as $country): ?> <li> <?= Html::encode("{$country->name} ({$country->code})") ?>: <?= $country->population ?> </li> <?php endforeach; ?> </ul> <?= LinkPager::widget([’pagination’ => $pagination]) ?> The view has two sections relative to displaying the country data. In the first part, the provided country data is traversed and rendered as an un- ordered HTML list. In the second part, a yiiwidgetsLinkPager widget is rendered using the pagination information passed from the action. The LinkPager widget displays a list of page buttons. Clicking on any of them will refresh the country data in the corresponding page. 2.5.6 Trying it Out To see how all of the above code works, use your browser to access the following URL: http://hostname/index.php?r=country/index
  • 44. 38 CHAPTER 2. GETTING STARTED At first, you will see a page showing five countries. Below the countries, you will see a pager with four buttons. If you click on the button “2”, you will see the page display another five countries in the database: the second page of records. Observe more carefully and you will find that the URL in the browser also changes to http://hostname/index.php?r=country/index&page=2 Behind the scenes, yiidataPagination is providing all of the necessary functionality to paginate a data set: • Initially, yiidataPagination represents the first page, which reflects the country SELECT query with the clause LIMIT 5 OFFSET 0. As a result, the first five countries will be fetched and displayed. • The yiiwidgetsLinkPager widget renders the page buttons using the URLs created by yiidataPagination::createUrl(). The URLs will contain the query parameter page, which represents the different page numbers. • If you click the page button “2”, a new request for the route country/ index will be triggered and handled. yiidataPagination reads the page query parameter from the URL and sets the current page number to 2. The new country query will thus have the clause LIMIT 5 OFFSET 5 and return the next five countries for display.
  • 45. 2.6. GENERATING CODE WITH GII 39 2.5.7 Summary In this section, you learned how to work with a database. You also learned how to fetch and display data in pages with the help of yiidataPagination and yiiwidgetsLinkPager. In the next section, you will learn how to use the powerful code gen- eration tool, called Gii, to help you rapidly implement some commonly re- quired features, such as the Create-Read-Update-Delete (CRUD) operations for working with the data in a database table. As a matter of fact, the code you have just written can all be automatically generated in Yii using the Gii tool. 2.6 Generating Code with Gii This section will describe how to use Gii to automatically generate code that implements some common Web site features. Using Gii to auto-generate code is simply a matter of entering the right information per to the instructions shown on the Gii Web pages. Through this tutorial, you will learn how to: • Enable Gii in your application • Use Gii to generate an Active Record class • Use Gii to generate the code implementing the CRUD operations for a DB table • Customize the code generated by Gii 2.6.1 Starting Gii Gii is provided in Yii as a module. You can enable Gii by configuring it in the yiibaseApplication::modules property of the application. Depending upon how you created your application, you may find the following code is already provided in the config/web.php configuration file: $config = [ ... ]; if (YII_ENV_DEV) { $config[’bootstrap’][] = ’gii’; $config[’modules’][’gii’] = ’yiigiiModule’; } The above configuration states that when in development environment, the application should include a module named gii, which is of class yiigii Module. If you check the entry script web/index.php of your application, you will find the following line, which essentially makes YII_ENV_DEV to be true.
  • 46. 40 CHAPTER 2. GETTING STARTED defined(’YII_ENV’) or define(’YII_ENV’, ’dev’); Thanks to that line, your application is in development mode, and will have already enabled Gii, per the above configuration. You can now access Gii via the following URL: http://hostname/index.php?r=gii Note: If you are accessing Gii from a machine other than local- host, the access will be denied by default for security purpose. You can configure Gii to add the allowed IP addresses as follows, ’gii’ => [ ’class’ => ’yiigiiModule’, ’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’ 192.168.178.20’] // adjust this to your needs ], 2.6.2 Generating an Active Record Class To use Gii to generate an Active Record class, select the “Model Generator” (by clicking the link on the Gii index page). Then fill out the form as follows: • Table Name: country • Model Class: Country
  • 47. 2.6. GENERATING CODE WITH GII 41 Next, click on the “Preview” button. You will see models/Country.php is listed in the resulting class file to be created. You may click on the name of the class file to preview its content. When using Gii, if you have already created the same file and would be overwriting it, click the diff button next to the file name to see the differences between the code to be generated and the existing version.
  • 48. 42 CHAPTER 2. GETTING STARTED When overwriting an existing file, check the box next to “overwrite” and then click the “Generate” button. If creating a new file, you can just click “Generate”. Next, you will see a confirmation page indicating the code has been successfully generated. If you had an existing file, you’ll also see a message indicating that it was overwritten with the newly generated code. 2.6.3 Generating CRUD Code CRUD stands for Create, Read, Update, and Delete, representing the four common tasks taken with data on most Web sites. To create CRUD func- tionality using Gii, select the “CRUD Generator” (by clicking the link on the Gii index page). For the “country” example, fill out the resulting form as follows: • Model Class: appmodelsCountry • Search Model Class: appmodelsCountrySearch • Controller Class: appcontrollersCountryController
  • 49. 2.6. GENERATING CODE WITH GII 43 Next, click on the “Preview” button. You will see a list of files to be generated, as shown below. If you previously created the controllers/CountryController.php and views /country/index.php files (in the databases section of the guide), check the “overwrite” box to replace them. (The previous versions did not have full CRUD support.)
  • 50. 44 CHAPTER 2. GETTING STARTED 2.6.4 Trying it Out To see how it works, use your browser to access the following URL: http://hostname/index.php?r=country/index You will see a data grid showing the countries from the database table. You may sort the grid, or filter it by entering filter conditions in the column headers. For each country displayed in the grid, you may choose to view its details, update it, or delete it. You may also click on the “Create Country” button on top of the grid to be provided with a form for creating a new country.
  • 51. 2.7. LOOKING AHEAD 45 The following is the list of the files generated by Gii, in case you want to investigate how these features are implemented, or to customize them: • Controller: controllers/CountryController.php • Models: models/Country.php and models/CountrySearch.php • Views: views/country/*.php Info: Gii is designed to be a highly customizable and extensible code generation tool. Using it wisely can greatly accelerate your application development speed. For more details, please refer to the Gii section. 2.6.5 Summary In this section, you have learned how to use Gii to generate the code that implements complete CRUD functionality for content stored in a database table. 2.7 Looking Ahead If you’ve read through the entire “Getting Started” section, you have now created a complete Yii application. In the process, you have learned how to implement some commonly needed features, such as getting data from users
  • 52. 46 CHAPTER 2. GETTING STARTED via an HTML form, fetching data from a database, and displaying data in a paginated fashion. You have also learned how to use Gii to generate code automatically. Using Gii for code generation turns the bulk of your Web development process into a task as simple as just filling out some forms. This section will summarize the Yii resources available to help you be more productive when using the framework. • Documentation – The Definitive Guide: As the name indicates, the guide precisely defines how Yii should work and provides general guidance about using Yii. It is the single most important Yii tutorial, and one that you should read before writing any Yii code. – The Class Reference: This specifies the usage of every class provided by Yii. It should be mainly used when you are writing code and want to understand the usage of a particular class, method, prop- erty. Usage of the class reference is best only after a contextual understanding of the entire framework. – The Wiki Articles: The wiki articles are written by Yii users based on their own experiences. Most of them are written like cookbook recipes, and show how to solve particular problems using Yii. While the quality of these articles may not be as good as the Definitive Guide, they are useful in that they cover broader topics and can often provide ready-to-use solutions. – Books • Extensions18: Yii boasts a library of thousands of user-contributed extensions that can be easily plugged into your applications, thereby making your application development even faster and easier. • Community – Forum: http://www.yiiframework.com/forum/ – IRC chat: The #yii channel on the freenode network (irc:// irc.freenode.net/yii) – GitHub: https://github.com/yiisoft/yii2 – Facebook: https://www.facebook.com/groups/yiitalk/ – Twitter: https://twitter.com/yiiframework – LinkedIn: https://www.linkedin.com/groups/yii-framework-1483367 18 http://www.yiiframework.com/extensions/
  • 53. Chapter 3 Application Structure 3.1 Overview Yii applications are organized according to the model-view-controller (MVC)1 design pattern. Models represent data, business logic and rules; views are output representation of models; and controllers take input and convert it to commands for models and views. Besides MVC, Yii applications also have the following entities: • entry scripts: they are PHP scripts that are directly accessible by end users. They are responsible for starting a request handling cycle. • applications: they are globally accessible objects that manage applic- ation components and coordinate them to fulfill requests. • application components: they are objects registered with applications and provide various services for fulfilling requests. • modules: they are self-contained packages that contain complete MVC by themselves. An application can be organized in terms of multiple modules. • filters: they represent code that need to be invoked before and after the actual handling of each request by controllers. • widgets: they are objects that can be embedded in views. They may contain controller logic and can be reused in different views. The following diagram shows the static structure of an application: 1 http://wikipedia.org/wiki/Model-view-controller 47
  • 54. 48 CHAPTER 3. APPLICATION STRUCTURE 3.2 Entry Scripts Entry scripts are the first chain in the application bootstrapping process. An application (either Web application or console application) has a single entry script. End users make requests to entry scripts which instantiate application instances and forward the requests to them. Entry scripts for Web applications must be stored under Web accessible directories so that they can be accessed by end users. They are often named as index.php, but can also use any other names, provided Web servers can locate them. Entry scripts for console applications are usually stored under the base path of applications and are named as yii (with the .php suffix). They should be made executable so that users can run console applications through the command ./yii <route> [arguments] [options]. Entry scripts mainly do the following work: • Define global constants; • Register Composer autoloader2; • Include the Yii class file; 2 http://getcomposer.org/doc/01-basic-usage.md#autoloading
  • 55. 3.2. ENTRY SCRIPTS 49 • Load application configuration; • Create and configure an application instance; • Call yiibaseApplication::run() to process the incoming request. 3.2.1 Web Applications The following is the code in the entry script for the Basic Web Application Template. <?php defined(’YII_DEBUG’) or define(’YII_DEBUG’, true); defined(’YII_ENV’) or define(’YII_ENV’, ’dev’); // register Composer autoloader require(__DIR__ . ’/../vendor/autoload.php’); // include Yii class file require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’); // load application configuration $config = require(__DIR__ . ’/../config/web.php’); // create, configure and run application (new yiiwebApplication($config))->run(); 3.2.2 Console Applications Similarly, the following is the code for the entry script of a console applica- tion: #!/usr/bin/env php <?php /** * Yii console bootstrap file. * * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ defined(’YII_DEBUG’) or define(’YII_DEBUG’, true); // fcgi doesn’t have STDIN and STDOUT defined by default defined(’STDIN’) or define(’STDIN’, fopen(’php://stdin’, ’r’)); defined(’STDOUT’) or define(’STDOUT’, fopen(’php://stdout’, ’w’)); // register Composer autoloader require(__DIR__ . ’/vendor/autoload.php’); // include Yii class file
  • 56. 50 CHAPTER 3. APPLICATION STRUCTURE require(__DIR__ . ’/vendor/yiisoft/yii2/Yii.php’); // load application configuration $config = require(__DIR__ . ’/config/console.php’); $application = new yiiconsoleApplication($config); $exitCode = $application->run(); exit($exitCode); 3.2.3 Defining Constants Entry scripts are the best place for defining global constants. Yii supports the following three constants: • YII_DEBUG: specifies whether the application is running in debug mode. When in debug mode, an application will keep more log information, and will reveal detailed error call stacks if exceptions are thrown. For this reason, debug mode should be used mainly during development. The default value of YII_DEBUG is false. • YII_ENV: specifies which environment the application is running in. This has been described in more detail in the Configurations section. The default value of YII_ENV is ’prod’, meaning the application is running in production environment. • YII_ENABLE_ERROR_HANDLER: specifies whether to enable the error handler provided by Yii. The default value of this constant is true. When defining a constant, we often use the code like the following: defined(’YII_DEBUG’) or define(’YII_DEBUG’, true); which is equivalent to the following code: if (!defined(’YII_DEBUG’)) { define(’YII_DEBUG’, true); } Clearly the former is more succinct and easier to understand. Constant definitions should be done at the very beginning of an entry script so that they can take effect when other PHP files are being included. 3.3 Applications Applications are objects that govern the overall structure and lifecycle of Yii application systems. Each Yii application system contains a single applic- ation object which is created in the entry script and is globally accessible through the expression Yii::$app.
  • 57. 3.3. APPLICATIONS 51 Info: Depending on the context, when we say “an application”, it can mean either an application object or an application system. There are two types of applications: yiiwebApplication and yiiconsole Application. As the names indicate, the former mainly handles Web re- quests while the latter console command requests. 3.3.1 Application Configurations When an entry script creates an application, it will load a configuration and apply it to the application, like the following: require(__DIR__ . ’/../vendor/autoload.php’); require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’); // load application configuration $config = require(__DIR__ . ’/../config/web.php’); // instantiate and configure the application (new yiiwebApplication($config))->run(); Like normal configurations, application configurations specify how to ini- tialize properties of application objects. Because application configurations are often very complex, they usually are kept in configuration files, like the web.php file in the above example. 3.3.2 Application Properties There are many important application properties that you should configure in application configurations. These properties typically describe the envir- onment that applications are running in. For example, applications need to know how to load controllers, where to store temporary files, etc. In the following, we will summarize these properties. Required Properties In any application, you should at least configure two properties: yiibase Application::id and yiibaseApplication::basePath. yiibaseApplication::id The yiibaseApplication::id property specifies a unique ID that differentiates an application from others. It is mainly used programmatically. Although not a requirement, for best inter- operability it is recommended that you use alphanumeric characters only when specifying an application ID.
  • 58. 52 CHAPTER 3. APPLICATION STRUCTURE yiibaseApplication::basePath The yiibaseApplication::basePath property specifies the root directory of an application. It is the directory that contains all protected source code of an application system. Under this dir- ectory, you normally will see sub-directories such as models, views, controllers, which contain source code corresponding to the MVC pattern. You may configure the yiibaseApplication::basePath property us- ing a directory path or a path alias. In both forms, the corresponding direct- ory must exist, or an exception will be thrown. The path will be normalized by calling the realpath() function. The yiibaseApplication::basePath property is often used to derive other important paths (e.g. the runtime path). For this reason, a path alias named @app is predefined to represent this path. Derived paths may then be formed using this alias (e.g. @app/runtime to refer to the runtime directory). Important Properties The properties described in this subsection often need to be configured be- cause they differ across different applications. yiibaseApplication::aliases This property allows you to define a set of aliases in terms of an array. The array keys are alias names, and the array values are the corresponding path definitions. For example, [ ’aliases’ => [ ’@name1’ => ’path/to/path1’, ’@name2’ => ’path/to/path2’, ], ] This property is provided such that you can define aliases in terms of applic- ation configurations instead of the method calls Yii::setAlias(). yiibaseApplication::bootstrap This is a very useful property. It allows you to specify an array of components that should be run during the application yiibaseApplication::bootstrap(). For example, if you want a module to customize the URL rules, you may list its ID as an element in this property. Each component listed in this property may be specified in one of the following formats: • an application component ID as specified via components. • a module ID as specified via modules. • a class name. • a configuration array.
  • 59. 3.3. APPLICATIONS 53 • an anonymous function that creates and returns a component. For example, [ ’bootstrap’ => [ // an application component ID or module ID ’demo’, // a class name ’appcomponentsProfiler’, // a configuration array [ ’class’ => ’appcomponentsProfiler’, ’level’ => 3, ], // an anonymous function function () { return new appcomponentsProfiler(); } ], ] Info: If a module ID is the same as an application component ID, the application component will be used during the bootstrapping process. If you want to use the module instead, you may specify it using an anonymous function like the following: >‘php [ function () { return Yii::$app->getModule(’user’); }, ] ‘ During the bootstrapping process, each component will be instantiated. If the component class implements yiibaseBootstrapInterface, its yii baseBootstrapInterface::bootstrap() method will also be called. Another practical example is in the application configuration for the Ba- sic Application Template, where the debug and gii modules are configured as bootstrapping components when the application is running in development environment, if (YII_ENV_DEV) { // configuration adjustments for ’dev’ environment $config[’bootstrap’][] = ’debug’; $config[’modules’][’debug’] = ’yiidebugModule’; $config[’bootstrap’][] = ’gii’; $config[’modules’][’gii’] = ’yiigiiModule’; }
  • 60. 54 CHAPTER 3. APPLICATION STRUCTURE Note: Putting too many components in bootstrap will degrade the performance of your application because for each request, the same set of components need to be run. So use bootstrapping components judiciously. yiiwebApplication::catchAll This property is supported by yiiweb Application only. It specifies a controller action which should handle all user requests. This is mainly used when the application is in maintenance mode and needs to handle all incoming requests via a single action. The configuration is an array whose first element specifies the route of the action. The rest of the array elements (key-value pairs) specify the parameters to be bound to the action. For example, [ ’catchAll’ => [ ’offline/notice’, ’param1’ => ’value1’, ’param2’ => ’value2’, ], ] yiibaseApplication::components This is the single most important property. It allows you to register a list of named components called applic- ation components that you can use in other places. For example, [ ’components’ => [ ’cache’ => [ ’class’ => ’yiicachingFileCache’, ], ’user’ => [ ’identityClass’ => ’appmodelsUser’, ’enableAutoLogin’ => true, ], ], ] Each application component is specified as a key-value pair in the array. The key represents the component ID, while the value represents the component class name or configuration. You can register any component with an application, and the component can later be accessed globally using the expression Yii::$app->ComponentID. Please read the Application Components section for details. yiibaseApplication::controllerMap This property allows you to map a controller ID to an arbitrary controller class. By default, Yii maps control- ler IDs to controller classes based on a convention (e.g. the ID post would be mapped to appcontrollersPostController). By configuring this property, you
  • 61. 3.3. APPLICATIONS 55 can break the convention for specific controllers. In the following example, account will be mapped to appcontrollersUserController, while article will be mapped to appcontrollersPostController. [ ’controllerMap’ => [ [ ’account’ => ’appcontrollersUserController’, ’article’ => [ ’class’ => ’appcontrollersPostController’, ’enableCsrfValidation’ => false, ], ], ], ] The array keys of this property represent the controller IDs, while the array values represent the corresponding controller class names or configurations. yiibaseApplication::controllerNamespace This property specifies the default namespace under which controller classes should be located. It defaults to appcontrollers. If a controller ID is post, by convention the cor- responding controller class name (without namespace) would be PostController , and the fully qualified class name would be appcontrollersPostController. Controller classes may also be located under sub-directories of the dir- ectory corresponding to this namespace. For example, given a controller ID admin/post, the corresponding fully qualified controller class would be app controllersadminPostController. It is important that the fully qualified controller classes should be auto- loadable and the actual namespace of your controller classes match the value of this property. Otherwise, you will receive “Page Not Found” error when accessing the application. In case you want to break the convention as described above, you may configure the controllerMap property. yiibaseApplication::language This property specifies the language in which the application should display content to end users. The default value of this property is en, meaning English. You should configure this property if your application needs to support multiple languages. The value of this property determines various internationalization as- pects, including message translation, date formatting, number formatting, etc. For example, the yiijuiDatePicker widget will use this property value by default to determine in which language the calendar should be dis- played and how should the date be formatted. It is recommended that you specify a language in terms of an IETF language tag3. For example, en stands for English, while en-US stands for 3 http://en.wikipedia.org/wiki/IETF_language_tag
  • 62. 56 CHAPTER 3. APPLICATION STRUCTURE English (United States). More details about this property can be found in the Internationalization section. yiibaseApplication::modules This property specifies the modules that the application contains. The property takes an array of module classes or configurations with the array keys being the module IDs. For example, [ ’modules’ => [ // a "booking" module specified with the module class ’booking’ => ’appmodulesbookingBookingModule’, // a "comment" module specified with a configuration array ’comment’ => [ ’class’ => ’appmodulescommentCommentModule’, ’db’ => ’db’, ], ], ] Please refer to the Modules section for more details. yiibaseApplication::name This property specifies the application name that may be displayed to end users. Unlike the yiibaseApplication:: id property which should take a unique value, the value of this property is mainly for display purpose and does not need to be unique. You do not always need to configure this property if none of your code is using it. yiibaseApplication::params This property specifies an array of glob- ally accessible application parameters. Instead of using hardcoded numbers and strings everywhere in your code, it is a good practice to define them as application parameters in a single place and use the parameters in places where needed. For example, you may define the thumbnail image size as a parameter like the following: [ ’params’ => [ ’thumbnail.size’ => [128, 128], ], ] Then in your code where you need to use the size value, you can simply use the code like the following: $size = Yii::$app->params[’thumbnail.size’]; $width = Yii::$app->params[’thumbnail.size’][0];
  • 63. 3.3. APPLICATIONS 57 Later if you decide to change the thumbnail size, you only need to modify it in the application configuration without touching any dependent code. yiibaseApplication::sourceLanguage This property specifies the lan- guage that the application code is written in. The default value is ’en-US’, meaning English (United States). You should configure this property if the text content in your code is not in English. Like the language property, you should configure this property in terms of an IETF language tag4. For example, en stands for English, while en-US stands for English (United States). More details about this property can be found in the Internationalization section. yiibaseApplication::timeZone This property is provided as an al- ternative way of setting the default time zone of PHP runtime. By configur- ing this property, you are essentially calling the PHP function date_default_timezone_set()5. For example, [ ’timeZone’ => ’America/Los_Angeles’, ] yiibaseApplication::version This property specifies the version of the application. It defaults to ’1.0’. You do not always need to configure this property if none of your code is using it. Useful Properties The properties described in this subsection are not commonly configured because their default values stipulate common conventions. However, you may still configure them in case you want to break the conventions. yiibaseApplication::charset This property specifies the charset that the application uses. The default value is ’UTF-8’ which should be kept as is for most applications unless you are working with some legacy systems that use a lot of non-unicode data. yiibaseApplication::defaultRoute This property specifies the route that an application should use when a request does not specify one. The route may consist of child module ID, controller ID, and/or action ID. For example, help, post/create, admin/post/create. If action ID is not given, it will take the default value as specified in yiibaseController::defaultAction. 4 http://en.wikipedia.org/wiki/IETF_language_tag 5 http://php.net/manual/en/function.date-default-timezone-set.php
  • 64. 58 CHAPTER 3. APPLICATION STRUCTURE For yiiwebApplication, the default value of this property is ’site’, which means the SiteController controller and its default action should be used. As a result, if you access the application without specifying a route, it will show the result of appcontrollersSiteController::actionIndex(). For yiiconsoleApplication, the default value is ’help’, which means the core command yiiconsolecontrollersHelpController::actionIndex() should be used. As a result, if you run the command yii without providing any arguments, it will display the help information. yiibaseApplication::extensions This property specifies the list of extensions that are installed and used by the application. By default, it will take the array returned by the file @vendor/yiisoft/extensions.php. The extensions.php file is generated and maintained automatically when you use Composer6 to install extensions. So in most cases, you do not need to con- figure this property. In the special case when you want to maintain extensions manually, you may configure this property like the following: [ ’extensions’ => [ [ ’name’ => ’extension name’, ’version’ => ’version number’, ’bootstrap’ => ’BootstrapClassName’, // optional, may also be a configuration array ’alias’ => [ // optional ’@alias1’ => ’to/path1’, ’@alias2’ => ’to/path2’, ], ], // ... more extensions like the above ... ], ] As you can see, the property takes an array of extension specifications. Each extension is specified with an array consisting of name and version elements. If an extension needs to run during the bootstrap process, a bootstrap element may be specified with a bootstrapping class name or a configuration array. An extension may also define a few aliases. yiibaseApplication::layout This property specifies the name of the default layout that should be used when rendering a view. The default value is ’main’, meaning the layout file main.php under the layout path should be used. If both of the layout path and the view path are taking the default 6 http://getcomposer.org
  • 65. 3.3. APPLICATIONS 59 values, the default layout file can be represented as the path alias @app/views /layouts/main.php. You may configure this property to be false if you want to disable layout by default, although this is very rare. yiibaseApplication::layoutPath This property specifies the path where layout files should be looked for. The default value is the layouts sub- directory under the view path. If the view path is taking its default value, the default layout path can be represented as the path alias @app/views/layouts. You may configure it as a directory or a path alias. yiibaseApplication::runtimePath This property specifies the path where temporary files, such as log files, cache files, can be generated. The default value is the directory represented by the alias @app/runtime. You may configure it as a directory or a path alias. Note that the runtime path must be writable by the process running the application. And the path should be protected from being accessed by end users because the temporary files under it may contain sensitive information. To simplify accessing to this path, Yii has predefined a path alias named @runtime for it. yiibaseApplication::viewPath This property specifies the root dir- ectory where view files are located. The default value is the directory rep- resented by the alias @app/views. You may configure it as a directory or a path alias. yiibaseApplication::vendorPath This property specifies the vendor directory managed by Composer7. It contains all third party libraries used by your application, including the Yii framework. The default value is the directory represented by the alias @app/vendor. You may configure this property as a directory or a path alias. When you modify this property, make sure you also adjust the Composer configuration accordingly. To simplify accessing to this path, Yii has predefined a path alias named @vendor for it. yiiconsoleApplication::enableCoreCommands This property is sup- ported by yiiconsoleApplication only. It specifies whether the core commands included in the Yii release should be enabled. The default value is true. 7 http://getcomposer.org
  • 66. 60 CHAPTER 3. APPLICATION STRUCTURE 3.3.3 Application Events An application triggers several events during the lifecycle of handling an request. You may attach event handlers to these events in application con- figurations like the following, [ ’on beforeRequest’ => function ($event) { // ... }, ] The use of the on eventName syntax is described in the Configurations section. Alternatively, you may attach event handlers during the bootstrapping process process after the application instance is created. For example, Yii::$app->on(yiibaseApplication::EVENT_BEFORE_REQUEST, function ($event ) { // ... }); yiibaseApplication::EVENT_BEFORE_REQUEST This event is triggered before an application handles a request. The actual event name is beforeRequest. When this event is triggered, the application instance has been configured and initialized. So it is a good place to insert your custom code via the event mechanism to intercept the request handling process. For example, in the event handler, you may dynamically set the yiibaseApplication:: language property based on some parameters. yiibaseApplication::EVENT_BEFORE_REQUEST This event is triggered after an application finishes handling a request but before sending the response. The actual event name is afterRequest. When this event is triggered, the request handling is completed and you may take this chance to do some postprocessing of the request or customize the response. Note that the yiiwebResponse component also triggers some events while it is sending out response content to end users. Those events are triggered after this event. yiibaseApplication::EVENT_BEFORE_REQUEST This event is triggered before running every controller action. The actual event name is beforeAction. The event parameter is an instance of yiibaseActionEvent. An event handler may set the yiibaseActionEvent::isValid property to be false to stop running the action. For example,
  • 67. 3.3. APPLICATIONS 61 [ ’on beforeAction’ => function ($event) { if (some condition) { $event->isValid = false; } else { } }, ] Note that the same beforeAction event is also triggered by modules and con- trollers. Application objects are the first ones triggering this event, followed by modules (if any), and finally controllers. If an event handler sets yiibase ActionEvent::isValid to be false, all the following events will NOT be triggered. yiibaseApplication::EVENT_BEFORE_REQUEST This event is triggered after running every controller action. The actual event name is afterAction. The event parameter is an instance of yiibaseActionEvent. Through the yiibaseActionEvent::result property, an event handler may access or modify the action result. For example, [ ’on afterAction’ => function ($event) { if (some condition) { // modify $event->result } else { } }, ] Note that the same afterAction event is also triggered by modules and con- trollers. These objects trigger this event in the reverse order as for that of beforeAction. That is, controllers are the first objects triggering this event, followed by modules (if any), and finally applications. 3.3.4 Application Lifecycle When an entry script is being executed to handle a request, an application will undergo the following lifecycle: 1. The entry script loads the application configuration as an array. 2. The entry script creates a new instance of the application: • yiibaseApplication::preInit() is called, which configures some high priority application properties, such as yiibaseApplication ::basePath. • Register the yiibaseApplication::errorHandler.
  • 68. 62 CHAPTER 3. APPLICATION STRUCTURE • Configure application properties. • yiibaseApplication::init() is called which further calls yii baseApplication::bootstrap() to run bootstrapping com- ponents. 3. The entry script calls yiibaseApplication::run() to run the ap- plication: • Trigger the yiibaseApplication::EVENT_BEFORE_REQUEST event. • Handle the request: resolve the request into a route and the associ- ated parameters; create the module, controller and action objects as specified by the route; and run the action. • Trigger the yiibaseApplication::EVENT_AFTER_REQUEST event. • Send response to the end user. 4. The entry script receives the exit status from the application and com- pletes the request processing. 3.4 Application Components Applications are service locators. They host a set of the so-called applica- tion components that provide different services for processing requests. For example, the urlManager component is responsible for routing Web requests to appropriate controllers; the db component provides DB-related services; and so on. Each application component has an ID that uniquely identifies itself among other application components in the same application. You can access an application component through the expression Yii::$app->componentID For example, you can use Yii::$app->db to get the yiidbConnection, and Yii::$app->cache to get the yiicachingCache registered with the applic- ation. An application component is created the first time it is accessed through the above expression. Any further accesses will return the same component instance. Application components can be any objects. You can register them by configuring the yiibaseApplication::components property in applica- tion configurations. For example, [ ’components’ => [ // register "cache" component using a class name ’cache’ => ’yiicachingApcCache’,
  • 69. 3.4. APPLICATION COMPONENTS 63 // register "db" component using a configuration array ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ], // register "search" component using an anonymous function ’search’ => function () { return new appcomponentsSolrService; }, ], ] Info: While you can register as many application components as you want, you should do this judiciously. Application com- ponents are like global variables. Using too many application components can potentially make your code harder to test and maintain. In many cases, you can simply create a local compon- ent and use it when needed. 3.4.1 Bootstrapping Components As mentioned above, an application component will only be instantiated when it is being accessed the first time. If it is not accessed at all during a request, it will not be instantiated. Sometimes, however, you may want to instantiate an application component for every request, even if it is not expli- citly accessed. To do so, you may list its ID in the yiibaseApplication ::bootstrap property of the application. For example, the following application configuration makes sure the log component is always loaded: [ ’bootstrap’ => [ ’log’, ], ’components’ => [ ’log’ => [ // configuration for "log" component ], ], ] 3.4.2 Core Application Components Yii defines a set of core application components with fixed IDs and default configurations. For example, the yiiwebApplication::request compon- ent is used to collect information about a user request and resolve it into
  • 70. 64 CHAPTER 3. APPLICATION STRUCTURE a route; the yiibaseApplication::db component represents a database connection through which you can perform database queries. It is with help of these core application components that Yii applications are able to handle user requests. Below is the list of the predefined core application components. You may configure and customize them like you do with normal application compon- ents. When you are configuring a core application component, if you do not specify its class, the default one will be used. • yiiwebAssetManager: manages asset bundles and asset publishing. Please refer to the Managing Assets section for more details. • yiidbConnection: represents a database connection through which you can perform DB queries. Note that when you configure this com- ponent, you must specify the component class as well as other required component properties, such as yiidbConnection::dsn. Please refer to the Data Access Objects section for more details. • yiibaseApplication::errorHandler: handles PHP errors and ex- ceptions. Please refer to the Handling Errors section for more details. • yiii18nFormatter: formats data when they are displayed to end users. For example, a number may be displayed with thousand separ- ator, a date may be formatted in long format. Please refer to the Data Formatting section for more details. • yiii18nI18N: supports message translation and formatting. Please refer to the Internationalization section for more details. • yiilogDispatcher: manages log targets. Please refer to the Logging section for more details. • yiiswiftmailerMailer: supports mail composing and sending. Please refer to the Mailing section for more details. • yiibaseApplication::response: represents the response being sent to end users. Please refer to the Responses section for more details. • yiibaseApplication::request: represents the request received from end users. Please refer to the Requests section for more details. • yiiwebSession: represents the session information. This compon- ent is only available in yiiwebApplication. Please refer to the Sessions and Cookies section for more details. • yiiwebUrlManager: supports URL parsing and creation. Please refer to the URL Parsing and Generation section for more details.
  • 71. 3.5. CONTROLLERS 65 • yiiwebUser: represents the user authentication information. This component is only available in yiiwebApplication Please refer to the Authentication section for more details. • yiiwebView: supports view rendering. Please refer to the Views section for more details. 3.5 Controllers Controllers are part of the MVC8 architecture. They are objects of classes extending from yiibaseController and are responsible for processing requests and generating responses. In particular, after taking over the control from applications, controllers will analyze incoming request data, pass them to models, inject model results into views, and finally generate outgoing responses. 3.5.1 Actions Controllers are composed by actions which are the most basic units that end users can address and request for execution. A controller can have one or multiple actions. The following example shows a post controller with two actions: view and create: namespace appcontrollers; use Yii; use appmodelsPost; use yiiwebController; use yiiwebNotFoundHttpException; class PostController extends Controller { public function actionView($id) { $model = Post::findOne($id); if ($model === null) { throw new NotFoundHttpException; } return $this->render(’view’, [ ’model’ => $model, ]); } public function actionCreate() { 8 http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
  • 72. 66 CHAPTER 3. APPLICATION STRUCTURE $model = new Post; if ($model->load(Yii::$app->request->post()) && $model->save()) { return $this->redirect([’view’, ’id’ => $model->id]); } else { return $this->render(’create’, [ ’model’ => $model, ]); } } } In the view action (defined by the actionView() method), the code first loads the model according to the requested model ID; If the model is loaded suc- cessfully, it will display it using a view named view. Otherwise, it will throw an exception. In the create action (defined by the actionCreate() method), the code is similar. It first tries to populate the model using the request data and save the model. If both succeed it will redirect the browser to the view action with the ID of the newly created model. Otherwise it will display the create view through which users can provide the needed input. 3.5.2 Routes End users address actions through the so-called routes. A route is a string that consists of the following parts: • a module ID: this exists only if the controller belongs to a non-application module; • a controller ID: a string that uniquely identifies the controller among all controllers within the same application (or the same module if the controller belongs to a module); • an action ID: a string that uniquely identifies the action among all actions within the same controller. Routes take the following format: ControllerID/ActionID or the following format if the controller belongs to a module: ModuleID/ControllerID/ActionID So if a user requests with the URL http://hostname/index.php?r=site/index, the index action in the site controller will be executed. For more details how routes are resolved into actions, please refer to the Routing section.
  • 73. 3.5. CONTROLLERS 67 3.5.3 Creating Controllers In yiiwebApplication, controllers should extend from yiiwebController or its child classes. Similarly in yiiconsoleApplication, controllers should extend from yiiconsoleController or its child classes. The fol- lowing code defines a site controller: namespace appcontrollers; use yiiwebController; class SiteController extends Controller { } Controller IDs Usually, a controller is designed to handle the requests regarding a particular type of resource. For this reason, controller IDs are often nouns referring to the types of the resources that they are handling. For example, you may use article as the ID of a controller that handles article data. By default, controller IDs should contain these characters only: English letters in lower case, digits, underscores, dashes and forward slashes. For example, article and post-comment are both valid controller IDs, while article ?, PostComment, adminpost are not. A controller ID may also contain a subdirectory prefix. For example, admin/article stands for an article controller in the admin subdirectory under the yiibaseApplication::controllerNamespace. Valid characters for subdirectory prefixes include: English letters in lower and upper cases, digits, underscores and forward slashes, where forward slashes are used as separators for multi-level subdirectories (e.g. panels/admin). Controller Class Naming Controller class names can be derived from controller IDs according to the following rules: • Turn the first letter in each word separated by dashes into upper case. Note that if the controller ID contains slashes, this rule only applies to the part after the last slash in the ID. • Remove dashes and replace any forward slashes with backward slashes. • Append the suffix Controller. • And prepend the yiibaseApplication::controllerNamespace.
  • 74. 68 CHAPTER 3. APPLICATION STRUCTURE The followings are some examples, assuming the yiibaseApplication:: controllerNamespace takes the default value appcontrollers: • article derives appcontrollersArticleController; • post-comment derives appcontrollersPostCommentController; • admin/post-comment derives appcontrollersadminPostCommentController; • adminPanels/post-comment derives appcontrollersadminPanelsPostCommentController . Controller classes must be autoloadable. For this reason, in the above ex- amples, the article controller class should be saved in the file whose alias is @app/controllers/ArticleController.php; while the admin/post2-comment con- troller should be in @app/controllers/admin/Post2CommentController.php. Info: The last example admin/post2-comment shows how you can put a controller under a sub-directory of the yiibaseApplication ::controllerNamespace. This is useful when you want to organ- ize your controllers into several categories and you do not want to use modules. Controller Map You can configure yiibaseApplication::controllerMap to overcome the constraints of the controller IDs and class names described above. This is mainly useful when you are using some third-party controllers which you do not control over their class names. You may configure yiibaseApplication::controllerMap in the ap- plication configuration like the following: [ ’controllerMap’ => [ // declares "account" controller using a class name ’account’ => ’appcontrollersUserController’, // declares "article" controller using a configuration array ’article’ => [ ’class’ => ’appcontrollersPostController’, ’enableCsrfValidation’ => false, ], ], ]
  • 75. 3.5. CONTROLLERS 69 Default Controller Each application has a default controller specified via the yiibaseApplication ::defaultRoute property. When a request does not specify a route, the route specified by this property will be used. For yiiwebApplication, its value is ’site’, while for yiiconsoleApplication, it is help. Therefore, if a URL is http://hostname/index.php, it means the site controller will handle the request. You may change the default controller with the following application configuration: [ ’defaultRoute’ => ’main’, ] 3.5.4 Creating Actions Creating actions can be as simple as defining the so-called action methods in a controller class. An action method is a public method whose name starts with the word action. The return value of an action method represents the response data to be sent to end users. The following code defines two actions index and hello-world: namespace appcontrollers; use yiiwebController; class SiteController extends Controller { public function actionIndex() { return $this->render(’index’); } public function actionHelloWorld() { return ’Hello World’; } } Action IDs An action is often designed to perform a particular manipulation about a resource. For this reason, action IDs are usually verbs, such as view, update, etc. By default, action IDs should contain these characters only: English let- ters in lower case, digits, underscores and dashes. The dashes in an actionID are used to separate words. For example, view, update2, comment-post are all valid action IDs, while view?, Update are not.
  • 76. 70 CHAPTER 3. APPLICATION STRUCTURE You can create actions in two ways: inline actions and standalone ac- tions. An inline action is defined as a method in the controller class, while a standalone action is a class extending yiibaseAction or its child class. Inline actions take less effort to create and are often preferred if you have no intention to reuse these actions. Standalone actions, on the other hand, are mainly created to be used in different controllers or be redistributed as extensions. Inline Actions Inline actions refer to the actions that are defined in terms of action methods as we just described. The names of the action methods are derived from action IDs according to the following criteria: • Turn the first letter in each word of the action ID into upper case; • Remove dashes; • Prepend the prefix action. For example, index becomes actionIndex, and hello-world becomes actionHelloWorld . Note: The names of the action methods are case-sensitive. If you have a method named ActionIndex, it will not be considered as an action method, and as a result, the request for the index action will result in an exception. Also note that action methods must be public. A private or protected method does NOT define an inline action. Inline actions are the most commonly defined actions because they take little effort to create. However, if you plan to reuse the same action in different places, or if you want to redistribute an action, you should consider defining it as a standalone action. Standalone Actions Standalone actions are defined in terms of action classes extending yiibase Action or its child classes. For example, in the Yii releases, there are yii webViewAction and yiiwebErrorAction, both of which are standalone actions. To use a standalone action, you should declare it in the action map by overriding the yiibaseController::actions() method in your controller classes like the following:
  • 77. 3.5. CONTROLLERS 71 public function actions() { return [ // declares "error" action using a class name ’error’ => ’yiiwebErrorAction’, // declares "view" action using a configuration array ’view’ => [ ’class’ => ’yiiwebViewAction’, ’viewPrefix’ => ’’, ], ]; } As you can see, the actions() method should return an array whose keys are action IDs and values the corresponding action class names or configurations. Unlike inline actions, action IDs for standalone actions can contain arbitrary characters, as long as they are declared in the actions() method. To create a standalone action class, you should extend yiibaseAction or its child class, and implement a public method named run(). The role of the run() method is similar to that of an action method. For example, <?php namespace appcomponents; use yiibaseAction; class HelloWorldAction extends Action { public function run() { return "Hello World"; } } Action Results The return value of an action method or the run() method of a standalone action is significant. It stands for the result of the corresponding action. The return value can be a response object which will be sent to as the response to end users. • For yiiwebApplication, the return value can also be some arbit- rary data which will be assigned to yiiwebResponse::data and be further converted into a string representing the response body. • For yiiconsoleApplication, the return value can also be an integer representing the yiiconsoleResponse::exitStatus of the command execution.
  • 78. 72 CHAPTER 3. APPLICATION STRUCTURE In the examples shown above, the action results are all strings which will be treated as the response body to be sent to end users. The following example shows how an action can redirect the user browser to a new URL by returning a response object (because the yiiwebController::redirect() method returns a response object): public function actionForward() { // redirect the user browser to http://example.com return $this->redirect(’http://example.com’); } Action Parameters The action methods for inline actions and the run() methods for standalone actions can take parameters, called action parameters. Their values are ob- tained from requests. For yiiwebApplication, the value of each action parameter is retrieved from $_GET using the parameter name as the key; for yiiconsoleApplication, they correspond to the command line argu- ments. In the following example, the view action (an inline action) has declared two parameters: $id and $version. namespace appcontrollers; use yiiwebController; class PostController extends Controller { public function actionView($id, $version = null) { // ... } } The action parameters will be populated as follows for different requests: • http://hostname/index.php?r=post/view&id=123: the $id parameter will be filled with the value ’123’, while $version is still null because there is no version query parameter. • http://hostname/index.php?r=post/view&id=123&version=2: the $id and $version parameters will be filled with ’123’ and ’2’, respectively. • http://hostname/index.php?r=post/view: a yiiwebBadRequestHttpException exception will be thrown because the required $id parameter is not provided in the request. • http://hostname/index.php?r=post/view&id[]=123: a yiiwebBadRequestHttpException exception will be thrown because $id parameter is receiving an unex- pected array value [’123’].
  • 79. 3.5. CONTROLLERS 73 If you want an action parameter to accept array values, you should type-hint it with array, like the following: public function actionView(array $id, $version = null) { // ... } Now if the request is http://hostname/index.php?r=post/view&id[]=123, the $id parameter will take the value of [’123’]. If the request is http://hostname /index.php?r=post/view&id=123, the $id parameter will still receive the same array value because the scalar value ’123’ will be automatically turned into an array. The above examples mainly show how action parameters work for Web applications. For console applications, please refer to the Console Commands section for more details. Default Action Each controller has a default action specified via the yiibaseController ::defaultAction property. When a route contains the controller ID only, it implies that the default action of the specified controller is requested. By default, the default action is set as index. If you want to change the default value, simply override this property in the controller class, like the following: namespace appcontrollers; use yiiwebController; class SiteController extends Controller { public $defaultAction = ’home’; public function actionHome() { return $this->render(’home’); } } 3.5.5 Controller Lifecycle When processing a request, an application will create a controller based on the requested route. The controller will then undergo the following lifecycle to fulfill the request: 1. The yiibaseController::init() method is called after the con- troller is created and configured.
  • 80. 74 CHAPTER 3. APPLICATION STRUCTURE 2. The controller creates an action object based on the requested action ID: • If the action ID is not specified, the yiibaseController:: defaultAction will be used. • If the action ID is found in the yiibaseController::actions(), a standalone action will be created; • If the action ID is found to match an action method, an inline action will be created; • Otherwise an yiibaseInvalidRouteException exception will be thrown. 3. The controller sequentially calls the beforeAction() method of the ap- plication, the module (if the controller belongs to a module) and the controller. • If one of the calls returns false, the rest of the uncalled beforeAction () will be skipped and the action execution will be cancelled. • By default, each beforeAction() method call will trigger a beforeAction event to which you can attach a handler. 4. The controller runs the action: • The action parameters will be analyzed and populated from the request data; 5. The controller sequentially calls the afterAction() method of the con- troller, the module (if the controller belongs to a module) and the application. • By default, each afterAction() method call will trigger an afterAction event to which you can attach a handler. 6. The application will take the action result and assign it to the response. 3.5.6 Best Practices In a well-designed application, controllers are often very thin with each action containing only a few lines of code. If your controller is rather complicated, it usually indicates that you should refactor it and move some code to other classes. In summary, controllers • may access the request data; • may call methods of models and other service components with request data;
  • 81. 3.6. MODELS 75 • may use views to compose responses; • should NOT process the request data - this should be done in models; • should avoid embedding HTML or other presentational code - this is better done in views. 3.6 Models Models are part of the MVC9 architecture. They are objects representing business data, rules and logic. You can create model classes by extending yiibaseModel or its child classes. The base class yiibaseModel supports many useful features: • Attributes: represent the business data and can be accessed like normal object properties or array elements; • Attribute labels: specify the display labels for attributes; • Massive assignment: supports populating multiple attributes in a single step; • Validation rules: ensures input data based on the declared validation rules; • Data Exporting: allows model data to be exported in terms of arrays with customizable formats. The Model class is also the base class for more advanced models, such as Active Record. Please refer to the relevant documentation for more details about these advanced models. Info: You are not required to base your model classes on yii baseModel. However, because there are many Yii components built to support yiibaseModel, it is usually the preferable base class for a model. 3.6.1 Attributes Models represent business data in terms of attributes. Each attribute is like a publicly accessible property of a model. The method yiibaseModel:: attributes() specifies what attributes a model class has. You can access an attribute like accessing a normal object property: 9 http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
  • 82. 76 CHAPTER 3. APPLICATION STRUCTURE $model = new appmodelsContactForm; // "name" is an attribute of ContactForm $model->name = ’example’; echo $model->name; You can also access attributes like accessing array elements, thanks to the support for ArrayAccess10 and ArrayIterator11 by yiibaseModel: $model = new appmodelsContactForm; // accessing attributes like array elements $model[’name’] = ’example’; echo $model[’name’]; // iterate attributes foreach ($model as $name => $value) { echo "$name: $valuen"; } Defining Attributes By default, if your model class extends directly from yiibaseModel, all its non-static public member variables are attributes. For example, the ContactForm model class below has four attributes: name, email, subject and body. The ContactForm model is used to represent the input data received from an HTML form. namespace appmodels; use yiibaseModel; class ContactForm extends Model { public $name; public $email; public $subject; public $body; } You may override yiibaseModel::attributes() to define attributes in a different way. The method should return the names of the attributes in a model. For example, yiidbActiveRecord does so by returning the column names of the associated database table as its attribute names. Note that you may also need to override the magic methods such as __get(), __set() so that the attributes can be accessed like normal object properties. 10 http://php.net/manual/en/class.arrayaccess.php 11 http://php.net/manual/en/class.arrayiterator.php
  • 83. 3.6. MODELS 77 Attribute Labels When displaying values or getting input for attributes, you often need to dis- play some labels associated with attributes. For example, given an attribute named firstName, you may want to display a label First Name which is more user-friendly when displayed to end users in places such as form inputs and error messages. You can get the label of an attribute by calling yiibaseModel::getAttributeLabel(). For example, $model = new appmodelsContactForm; // displays "Name" echo $model->getAttributeLabel(’name’); By default, attribute labels are automatically generated from attribute names. The generation is done by the method yiibaseModel::generateAttributeLabel(). It will turn camel-case variable names into multiple words with the first let- ter in each word in upper case. For example, username becomes Username, and firstName becomes First Name. If you do not want to use automatically generated labels, you may over- ride yiibaseModel::attributeLabels() to explicitly declare attribute labels. For example, namespace appmodels; use yiibaseModel; class ContactForm extends Model { public $name; public $email; public $subject; public $body; public function attributeLabels() { return [ ’name’ => ’Your name’, ’email’ => ’Your email address’, ’subject’ => ’Subject’, ’body’ => ’Content’, ]; } } For applications supporting multiple languages, you may want to translate attribute labels. This can be done in the yiibaseModel::attributeLabels() method as well, like the following: public function attributeLabels() {
  • 84. 78 CHAPTER 3. APPLICATION STRUCTURE return [ ’name’ => Yii::t(’app’, ’Your name’), ’email’ => Yii::t(’app’, ’Your email address’), ’subject’ => Yii::t(’app’, ’Subject’), ’body’ => Yii::t(’app’, ’Content’), ]; } You may even conditionally define attribute labels. For example, based on the scenario the model is being used in, you may return different labels for the same attribute. Info: Strictly speaking, attribute labels are part of views. But declaring labels in models is often very convenient and can result in very clean and reusable code. 3.6.2 Scenarios A model may be used in different scenarios. For example, a User model may be used to collect user login inputs, but it may also be used for the user registration purpose. In different scenarios, a model may use different business rules and logic. For example, the email attribute may be required during user registration, but not so during user login. A model uses the yiibaseModel::scenario property to keep track of the scenario it is being used in. By default, a model supports only a single scenario named default. The following code shows two ways of setting the scenario of a model: // scenario is set as a property $model = new User; $model->scenario = ’login’; // scenario is set through configuration $model = new User([’scenario’ => ’login’]); By default, the scenarios supported by a model are determined by the valid- ation rules declared in the model. However, you can customize this behavior by overriding the yiibaseModel::scenarios() method, like the follow- ing: namespace appmodels; use yiidbActiveRecord; class User extends ActiveRecord { public function scenarios() { return [ ’login’ => [’username’, ’password’], ’register’ => [’username’, ’email’, ’password’],
  • 85. 3.6. MODELS 79 ]; } } Info: In the above and following examples, the model classes are extending from yiidbActiveRecord because the usage of multiple scenarios usually happens to Active Record classes. The scenarios() method returns an array whose keys are the scenario names and values the corresponding active attributes. An active attribute can be massively assigned and is subject to validation. In the above example, the username and password attributes are active in the login scenario; while in the register scenario, email is also active besides username and password. The default implementation of scenarios() will return all scenarios found in the validation rule declaration method yiibaseModel::rules(). When overriding scenarios(), if you want to introduce new scenarios in addition to the default ones, you may write code like the following: namespace appmodels; use yiidbActiveRecord; class User extends ActiveRecord { public function scenarios() { $scenarios = parent::scenarios(); $scenarios[’login’] = [’username’, ’password’]; $scenarios[’register’] = [’username’, ’email’, ’password’]; return $scenarios; } } The scenario feature is primarily used by validation and massive attribute assignment. You can, however, use it for other purposes. For example, you may declare attribute labels differently based on the current scenario. 3.6.3 Validation Rules When the data for a model is received from end users, it should be validated to make sure it satisfies certain rules (called validation rules, also known as business rules). For example, given a ContactForm model, you may want to make sure all attributes are not empty and the email attribute contains a valid email address. If the values for some attributes do not satisfy the corresponding business rules, appropriate error messages should be displayed to help the user to fix the errors. You may call yiibaseModel::validate() to validate the received data. The method will use the validation rules declared in yiibaseModel
  • 86. 80 CHAPTER 3. APPLICATION STRUCTURE ::rules() to validate every relevant attribute. If no error is found, it will return true. Otherwise, it will keep the errors in the yiibaseModel:: errors property and return false. For example, $model = new appmodelsContactForm; // populate model attributes with user inputs $model->attributes = Yii::$app->request->post(’ContactForm’); if ($model->validate()) { // all inputs are valid } else { // validation failed: $errors is an array containing error messages $errors = $model->errors; } To declare validation rules associated with a model, override the yiibase Model::rules() method by returning the rules that the model attributes should satisfy. The following example shows the validation rules declared for the ContactForm model: public function rules() { return [ // the name, email, subject and body attributes are required [[’name’, ’email’, ’subject’, ’body’], ’required’], // the email attribute should be a valid email address [’email’, ’email’], ]; } A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules. Please refer to the Validating Input section for more details on how to declare validation rules. Sometimes, you may want a rule to be applied only in certain scenarios. To do so, you can specify the on property of a rule, like the following: public function rules() { return [ // username, email and password are all required in "register" scenario [[’username’, ’email’, ’password’], ’required’, ’on’ => ’register’], // username and password are required in "login" scenario [[’username’, ’password’], ’required’, ’on’ => ’login’], ]; } If you do not specify the on property, the rule would be applied in all scen- arios. A rule is called an active rule if it can be applied in the current yii baseModel::scenario.
  • 87. 3.6. MODELS 81 An attribute will be validated if and only if it is an active attribute declared in scenarios() and is associated with one or multiple active rules declared in rules(). 3.6.4 Massive Assignment Massive assignment is a convenient way of populating a model with user inputs using a single line of code. It populates the attributes of a model by assigning the input data directly to the yiibaseModel::$attributes property. The following two pieces of code are equivalent, both trying to as- sign the form data submitted by end users to the attributes of the ContactForm model. Clearly, the former, which uses massive assignment, is much cleaner and less error prone than the latter: $model = new appmodelsContactForm; $model->attributes = Yii::$app->request->post(’ContactForm’); $model = new appmodelsContactForm; $data = Yii::$app->request->post(’ContactForm’, []); $model->name = isset($data[’name’]) ? $data[’name’] : null; $model->email = isset($data[’email’]) ? $data[’email’] : null; $model->subject = isset($data[’subject’]) ? $data[’subject’] : null; $model->body = isset($data[’body’]) ? $data[’body’] : null; Safe Attributes Massive assignment only applies to the so-called safe attributes which are the attributes listed in yiibaseModel::scenarios() for the current yii baseModel::scenario of a model. For example, if the User model has the following scenario declaration, then when the current scenario is login, only the username and password can be massively assigned. Any other attributes will be kept untouched. public function scenarios() { return [ ’login’ => [’username’, ’password’], ’register’ => [’username’, ’email’, ’password’], ]; } Info: The reason that massive assignment only applies to safe attributes is because you want to control which attributes can be modified by end user data. For example, if the User model has a permission attribute which determines the permission assigned to the user, you would like this attribute to be modifiable by administrators through a backend interface only.
  • 88. 82 CHAPTER 3. APPLICATION STRUCTURE Because the default implementation of yiibaseModel::scenarios() will return all scenarios and attributes found in yiibaseModel::rules(), if you do not override this method, it means an attribute is safe as long as it appears in one of the active validation rules. For this reason, a special validator aliased safe is provided so that you can declare an attribute to be safe without actually validating it. For example, the following rules declare that both title and description are safe attributes. public function rules() { return [ [[’title’, ’description’], ’safe’], ]; } Unsafe Attributes As described above, the yiibaseModel::scenarios() method serves for two purposes: determining which attributes should be validated, and de- termining which attributes are safe. In some rare cases, you may want to validate an attribute but do not want to mark it safe. You can do so by prefixing an exclamation mark ! to the attribute name when declaring it in scenarios(), like the secret attribute in the following: public function scenarios() { return [ ’login’ => [’username’, ’password’, ’!secret’], ]; } When the model is in the login scenario, all three attributes will be validated. However, only the username and password attributes can be massively assigned. To assign an input value to the secret attribute, you have to do it explicitly as follows, $model->secret = $secret; 3.6.5 Data Exporting Models often need to be exported in different formats. For example, you may want to convert a collection of models into JSON or Excel format. The exporting process can be broken down into two independent steps. In the first step, models are converted into arrays; in the second step, the arrays are converted into target formats. You may just focus on the first step, because the second step can be achieved by generic data formatters, such as yiiweb JsonResponseFormatter. The simplest way of converting a model into an array is to use the yii baseModel::$attributes property. For example,
  • 89. 3.6. MODELS 83 $post = appmodelsPost::findOne(100); $array = $post->attributes; By default, the yiibaseModel::$attributes property will return the val- ues of all attributes declared in yiibaseModel::attributes(). A more flexible and powerful way of converting a model into an array is to use the yiibaseModel::toArray() method. Its default behavior is the same as that of yiibaseModel::$attributes. However, it allows you to choose which data items, called fields, to be put in the resulting array and how they should be formatted. In fact, it is the default way of exporting models in RESTful Web service development, as described in the Response Formatting. Fields A field is simply a named element in the array that is obtained by calling the yiibaseModel::toArray() method of a model. By default, field names are equivalent to attribute names. However, you can change this behavior by overriding the yiibaseModel::fields() an- d/or yiibaseModel::extraFields() methods. Both methods should re- turn a list of field definitions. The fields defined by fields() are default fields, meaning that toArray() will return these fields by default. The extraFields() method defines additionally available fields which can also be returned by toArray() as long as you specify them via the $expand parameter. For ex- ample, the following code will return all fields defined in fields() and the prettyName and fullAddress fields if they are defined in extraFields(). $array = $model->toArray([], [’prettyName’, ’fullAddress’]); You can override fields() to add, remove, rename or redefine fields. The return value of fields() should be an array. The array keys are the field names, and the array values are the corresponding field definitions which can be either property/attribute names or anonymous functions returning the corresponding field values. In the special case when a field name is the same as its defining attribute name, you can omit the array key. For example, // explicitly list every field, best used when you want to make sure the changes // in your DB table or model attributes do not cause your field changes (to keep API backward compatibility). public function fields() { return [ // field name is the same as the attribute name ’id’, // field name is "email", the corresponding attribute name is " email_address" ’email’ => ’email_address’,
  • 90. 84 CHAPTER 3. APPLICATION STRUCTURE // field name is "name", its value is defined by a PHP callback ’name’ => function () { return $this->first_name . ’ ’ . $this->last_name; }, ]; } // filter out some fields, best used when you want to inherit the parent implementation // and blacklist some sensitive fields. public function fields() { $fields = parent::fields(); // remove fields that contain sensitive information unset($fields[’auth_key’], $fields[’password_hash’], $fields[’ password_reset_token’]); return $fields; } Warning: Because by default all attributes of a model will be included in the exported array, you should examine your data to make sure they do not contain sensitive information. If there is such information, you should override fields() to filter them out. In the above example, we choose to filter out auth_key, password_hash and password_reset_token. 3.6.6 Best Practices Models are the central places to represent business data, rules and logic. They often need to be reused in different places. In a well-designed applica- tion, models are usually much fatter than controllers. In summary, models • may contain attributes to represent business data; • may contain validation rules to ensure the data validity and integrity; • may contain methods implementing business logic; • should NOT directly access request, session, or any other environ- mental data. These data should be injected by controllers into models; • should avoid embedding HTML or other presentational code - this is better done in views; • avoid having too many scenarios in a single model.
  • 91. 3.7. VIEWS 85 You may usually consider the last recommendation above when you are de- veloping large complex systems. In these systems, models could be very fat because they are used in many places and may thus contain many sets of rules and business logic. This often ends up in a nightmare in maintaining the model code because a single touch of the code could affect several dif- ferent places. To make the mode code more maintainable, you may take the following strategy: • Define a set of base model classes that are shared by different applica- tions or modules. These model classes should contain minimal sets of rules and logic that are common among all their usages. • In each application or module that uses a model, define a concrete model class by extending from the corresponding base model class. The concrete model classes should contain rules and logic that are specific for that application or module. For example, in the Advanced Application Template, you may define a base model class commonmodelsPost. Then for the front end application, you define and use a concrete model class frontendmodelsPost which extends from commonmodelsPost. And similarly for the back end application, you define backendmodelsPost. With this strategy, you will be sure that the code in frontendmodelsPost is only specific to the front end application, and if you make any change to it, you do not need to worry if the change may break the back end application. 3.7 Views Views are part of the MVC12 architecture. They are code responsible for presenting data to end users. In a Web application, views are usually created in terms of view templates which are PHP script files containing mainly HTML code and presentational PHP code. They are managed by the yii webView application component which provides commonly used methods to facilitate view composition and rendering. For simplicity, we often call view templates or view template files as views. 3.7.1 Creating Views As aforementioned, a view is simply a PHP script mixed with HTML and PHP code. The following is the view that presents a login form. As you can see, PHP code is used to generate the dynamic content, such as the page title and the form, while HTML code organizes them into a presentable HTML page. 12 http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
  • 92. 86 CHAPTER 3. APPLICATION STRUCTURE <?php use yiihelpersHtml; use yiiwidgetsActiveForm; /* @var $this yiiwebView */ /* @var $form yiiwidgetsActiveForm */ /* @var $model appmodelsLoginForm */ $this->title = ’Login’; ?> <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out the following fields to login:</p> <?php $form = ActiveForm::begin(); ?> <?= $form->field($model, ’username’) ?> <?= $form->field($model, ’password’)->passwordInput() ?> <?= Html::submitButton(’Login’) ?> <?php ActiveForm::end(); ?> Within a view, you can access $this which refers to the yiiwebView man- aging and rendering this view template. Besides $this, there may be other predefined variables in a view, such as $model in the above example. These variables represent the data that are pushed into the view by controllers or other objects whose trigger the view rendering. Tip: The predefined variables are listed in a comment block at beginning of a view so that they can be recognized by IDEs. It is also a good way of documenting your views. Security When creating views that generate HTML pages, it is important that you encode and/or filter the data coming from end users before presenting them. Otherwise, your application may be subject to cross-site scripting13 attacks. To display a plain text, encode it first by calling yiihelpersHtml:: encode(). For example, the following code encodes the user name before displaying it: <?php use yiihelpersHtml; ?> <div class="username"> <?= Html::encode($user->name) ?> </div> 13 http://en.wikipedia.org/wiki/Cross-site_scripting
  • 93. 3.7. VIEWS 87 To display HTML content, use yiihelpersHtmlPurifier to filter the con- tent first. For example, the following code filters the post content before displaying it: <?php use yiihelpersHtmlPurifier; ?> <div class="post"> <?= HtmlPurifier::process($post->text) ?> </div> Tip: While HTMLPurifier does excellent job in making output safe, it is not fast. You should consider caching the filtering result if your application requires high performance. Organizing Views Like controllers and models, there are conventions to organize views. • For views rendered by a controller, they should be put under the dir- ectory @app/views/ControllerID by default, where ControllerID refers to the controller ID. For example, if the controller class is PostController, the directory would be @app/views/post; If it is PostCommentController, the directory would be @app/views/post-comment. In case the controller belongs to a module, the directory would be views/ControllerID under the yiibaseModule::basePath. • For views rendered in a widget, they should be put under the WidgetPath /views directory by default, where WidgetPath stands for the directory containing the widget class file. • For views rendered by other objects, it is recommended that you follow the similar convention as that for widgets. You may customize these default view directories by overriding the yiibase ViewContextInterface::getViewPath() method of controllers or widgets. 3.7.2 Rendering Views You can render views in controllers, widgets, or any other places by calling view rendering methods. These methods share a similar signature shown as follows, /** * @param string $view view name or file path, depending on the actual rendering method * @param array $params the data to be passed to the view * @return string rendering result
  • 94. 88 CHAPTER 3. APPLICATION STRUCTURE */ methodName($view, $params = []) Rendering in Controllers Within controllers, you may call the following controller methods to render views: • yiibaseController::render(): renders a named view and applies a layout to the rendering result. • yiibaseController::renderPartial(): renders a named view without any layout. • yiiwebController::renderAjax(): renders a named view without any layout, and injects all registered JS/CSS scripts and files. It is usually used in response to AJAX Web requests. • yiibaseController::renderFile(): renders a view specified in terms of a view file path or alias. For example, namespace appcontrollers; use Yii; use appmodelsPost; use yiiwebController; use yiiwebNotFoundHttpException; class PostController extends Controller { public function actionView($id) { $model = Post::findOne($id); if ($model === null) { throw new NotFoundHttpException; } // renders a view named "view" and applies a layout to it return $this->render(’view’, [ ’model’ => $model, ]); } } Rendering in Widgets Within widgets, you may call the following widget methods to render views.
  • 95. 3.7. VIEWS 89 • yiibaseWidget::render(): renders a named view. • yiibaseWidget::renderFile(): renders a view specified in terms of a view file path or alias. For example, namespace appcomponents; use yiibaseWidget; use yiihelpersHtml; class ListWidget extends Widget { public $items = []; public function run() { // renders a view named "list" return $this->render(’list’, [ ’items’ => $this->items, ]); } } Rendering in Views You can render a view within another view by calling one of the following methods provided by the yiibaseView: • yiibaseView::render(): renders a named view. • yiiwebView::renderAjax(): renders a named view and injects all registered JS/CSS scripts and files. It is usually used in response to AJAX Web requests. • yiibaseView::renderFile(): renders a view specified in terms of a view file path or alias. For example, the following code in a view renders the _overview.php view file which is in the same directory as the view being currently rendered. Remember that $this in a view refers to the yiibaseView component: <?= $this->render(’_overview’) ?> Rendering in Other Places In any place, you can get access to the yiibaseView application com- ponent by the expression Yii::$app->view and then call its aforementioned methods to render a view. For example,
  • 96. 90 CHAPTER 3. APPLICATION STRUCTURE // displays the view file "@app/views/site/license.php" echo Yii::$app->view->renderFile(’@app/views/site/license.php’); Named Views When you render a view, you can specify the view using either a view name or a view file path/alias. In most cases, you would use the former because it is more concise and flexible. We call views specified using names as named views. A view name is resolved into the corresponding view file path according to the following rules: • A view name may omit the file extension name. In this case, .php will be used as the extension. For example, the view name about corresponds to the file name about.php. • If the view name starts with double slashes //, the corresponding view file path would be @app/views/ViewName. That is, the view is looked for under the yiibaseApplication::viewPath. For example, //site/ about will be resolved into @app/views/site/about.php. • If the view name starts with a single slash /, the view file path is formed by prefixing the view name with the yiibaseModule::viewPath of the currently active module. If there is no active module, @app/views/ ViewName will be used. For example, /user/create will be resolved into @app/modules/user/views/user/create.php, if the currently active module is user. If there is no active module, the view file path would be @app/ views/user/create.php. • If the view is rendered with a yiibaseView::context and the con- text implements yiibaseViewContextInterface, the view file path is formed by prefixing the yiibaseViewContextInterface::getViewPath() of the context to the view name. This mainly applies to the views rendered within controllers and widgets. For example, site/about will be resolved into @app/views/site/about.php if the context is the control- ler SiteController. • If a view is rendered within another view, the directory containing the other view file will be prefixed to the new view name to form the actual view file path. For example, item will be resolved into @app/views/post /item if it is being rendered in the view @app/views/post/index.php. According to the above rules, calling $this->render(’view’) in a controller app controllersPostController will actually render the view file @app/views/post/ view.php, while calling $this->render(’_overview’) in that view will render the view file @app/views/post/_overview.php.
  • 97. 3.7. VIEWS 91 Accessing Data in Views There are two approaches to access data within a view: push and pull. By passing the data as the second parameter to the view rendering meth- ods, you are using the push approach. The data should be represented as an array of name-value pairs. When the view is being rendered, the PHP extract() function will be called on this array so that the array is extracted into variables in the view. For example, the following view rendering code in a controller will push two variables to the report view: $foo = 1 and $bar = 2. echo $this->render(’report’, [ ’foo’ => 1, ’bar’ => 2, ]); The pull approach actively retrieves data from the yiibaseView or other objects accessible in views (e.g. Yii::$app). Using the code below as an example, within the view you can get the controller object by the expression $this->context. And as a result, it is possible for you to access any properties or methods of the controller in the report view, such as the controller ID shown in the following: The controller ID is: <?= $this->context->id ?> ?> The push approach is usually the preferred way of accessing data in views, because it makes views less dependent on context objects. Its drawback is that you need to manually build the data array all the time, which could become tedious and error prone if a view is shared and rendered in different places. Sharing Data among Views The yiibaseView provides the yiibaseView::params property that you can use to share data among views. For example, in an about view, you can have the following code which specifies the current segment of the breadcrumbs. $this->params[’breadcrumbs’][] = ’About Us’; Then, in the layout file, which is also a view, you can display the breadcrumbs using the data passed along yiibaseView::params: <?= yiiwidgetsBreadcrumbs::widget([ ’links’ => isset($this->params[’breadcrumbs’]) ? $this->params[’ breadcrumbs’] : [], ]) ?>
  • 98. 92 CHAPTER 3. APPLICATION STRUCTURE 3.7.3 Layouts Layouts are a special type of views that represent the common parts of multiple views. For example, the pages for most Web applications share the same page header and footer. While you can repeat the same page header and footer in every view, a better way is to do this once in a layout and embed the rendering result of a content view at an appropriate place in the layout. Creating Layouts Because layouts are also views, they can be created in the similar way as normal views. By default, layouts are stored in the directory @app/views/ layouts. For layouts used within a module, they should be stored in the views/layouts directory under the yiibaseModule::basePath. You may customize the default layout directory by configuring the yiibaseModule ::layoutPath property of the application or modules. The following example shows how a layout looks like. Note that for illustrative purpose, we have greatly simplified the code in the layout. In practice, you may want to add more content to it, such as head tags, main menu, etc. <?php use yiihelpersHtml; /* @var $this yiiwebView */ /* @var $content string */ ?> <?php $this->beginPage() ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <?= Html::csrfMetaTags() ?> <title><?= Html::encode($this->title) ?></title> <?php $this->head() ?> </head> <body> <?php $this->beginBody() ?> <header>My Company</header> <?= $content ?> <footer>&copy; 2014 by My Company</footer> <?php $this->endBody() ?> </body> </html> <?php $this->endPage() ?> As you can see, the layout generates the HTML tags that are common to all pages. Within the <body> section, the layout echoes the $content variable which represents the rendering result of content views and is pushed into the
  • 99. 3.7. VIEWS 93 layout when yiibaseController::render() is called. Most layouts should call the following methods like shown in the above code. These methods mainly trigger events about the rendering process so that scripts and tags registered in other places can be properly injected into the places where these methods are called. • yiibaseView::beginPage(): This method should be called at the very beginning of the layout. It triggers the yiibaseView::EVENT_BEGIN_PAGE event which indicates the beginning of a page. • yiibaseView::endPage(): This method should be called at the end of the layout. It triggers the yiibaseView::EVENT_END_PAGE event which indicates the end of a page. • yiiwebView::head(): This method should be called within the < head> section of an HTML page. It generates a placeholder which will be replaced with the registered head HTML code (e.g. link tags, meta tags) when a page finishes rendering. • yiiwebView::beginBody(): This method should be called at the be- ginning of the <body> section. It triggers the yiiwebView::EVENT_BEGIN_BODY event and generates a placeholder which will be replaced by the re- gistered HTML code (e.g. JavaScript) targeted at the body begin position. • yiiwebView::endBody(): This method should be called at the end of the <body> section. It triggers the yiiwebView::EVENT_END_BODY event and generates a placeholder which will be replaced by the re- gistered HTML code (e.g. JavaScript) targeted at the body end posi- tion. Accessing Data in Layouts Within a layout, you have access to two predefined variables: $this and $content. The former refers to the yiibaseView component, like in nor- mal views, while the latter contains the rendering result of a content view which is rendered by calling the yiibaseController::render() method in controllers. If you want to access other data in layouts, you have to use the pull method as described in the Accessing Data in Views subsection. If you want to pass data from a content view to a layout, you may use the method described in the Sharing Data among Views subsection. Using Layouts As described in the Rendering in Controllers subsection, when you render a view by calling the yiibaseController::render() method in a control-
  • 100. 94 CHAPTER 3. APPLICATION STRUCTURE ler, a layout will be applied to the rendering result. By default, the layout @app/views/layouts/main.php will be used. You may use a different layout by configuring either yiibaseApplication ::layout or yiibaseController::layout. The former governs the lay- out used by all controllers, while the latter overrides the former for individual controllers. For example, the following code makes the post controller to use @app/views/layouts/post.php as the layout when rendering its views. Other controllers, assuming their layout property is untouched, will still use the default @app/views/layouts/main.php as the layout. namespace appcontrollers; use yiiwebController; class PostController extends Controller { public $layout = ’post’; // ... } For controllers belonging to a module, you may also configure the module’s yiibaseModule::layout property to use a particular layout for these con- trollers. Because the layout property may be configured at different levels (control- lers, modules, application), behind the scene Yii takes two steps to determine what is the actual layout file being used for a particular controller. In the first step, it determines the layout value and the context module: • If the yiibaseController::layout property of the controller is not null, use it as the layout value and the yiibaseController::module of the controller as the context module. • If yiibaseController::layout is null, search through all ancestor modules (including the application itself) of the controller and find the first module whose yiibaseModule::layout property is not null. Use that module and its yiibaseModule::layout value as the con- text module and the chosen layout value. If such a module cannot be found, it means no layout will be applied. In the second step, it determines the actual layout file according to the layout value and the context module determined in the first step. The layout value can be: • a path alias (e.g. @app/views/layouts/main). • an absolute path (e.g. /main): the layout value starts with a slash. The actual layout file will be looked for under the application’s yiibase Application::layoutPath which defaults to @app/views/layouts.
  • 101. 3.7. VIEWS 95 • a relative path (e.g. main): the actual layout file will be looked for under the context module’s yiibaseModule::layoutPath which defaults to the views/layouts directory under the yiibaseModule::basePath. • the boolean value false: no layout will be applied. If the layout value does not contain a file extension, it will use the default one .php. Nested Layouts Sometimes you may want to nest one layout in another. For example, in different sections of a Web site, you want to use different layouts, while all these layouts share the same basic layout that generates the overall HTML5 page structure. You can achieve this goal by calling yiibaseView ::beginContent() and yiibaseView::endContent() in the child layouts like the following: <?php $this->beginContent(’@app/views/layouts/base.php’); ?> ...child layout content here... <?php $this->endContent(); ?> As shown above, the child layout content should be enclosed within yii baseView::beginContent() and yiibaseView::endContent(). The parameter passed to yiibaseView::beginContent() specifies what is the parent layout. It can be either a layout file or alias. Using the above approach, you can nest layouts in more than one levels. Using Blocks Blocks allow you to specify the view content in one place while displaying it in another. They are often used together with layouts. For example, you can define a block in a content view and display it in the layout. You call yiibaseView::beginBlock() and yiibaseView::endBlock() to define a block. The block can then be accessed via $view->blocks[$blockID ], where $blockID stands for a unique ID that you assign to the block when defining it. The following example shows how you can use blocks to customize specific parts of a layout in a content view. First, in a content view, define one or multiple blocks: ... <?php $this->beginBlock(’block1’); ?> ...content of block1...
  • 102. 96 CHAPTER 3. APPLICATION STRUCTURE <?php $this->endBlock(); ?> ... <?php $this->beginBlock(’block3’); ?> ...content of block3... <?php $this->endBlock(); ?> Then, in the layout view, render the blocks if they are available, or display some default content if a block is not defined. ... <?php if (isset($this->blocks[’block1’])): ?> <?= $this->blocks[’block1’] ?> <?php else: ?> ... default content for block1 ... <?php endif; ?> ... <?php if (isset($this->blocks[’block2’])): ?> <?= $this->blocks[’block2’] ?> <?php else: ?> ... default content for block2 ... <?php endif; ?> ... <?php if (isset($this->blocks[’block3’])): ?> <?= $this->blocks[’block3’] ?> <?php else: ?> ... default content for block3 ... <?php endif; ?> ... 3.7.4 Using View Components yiibaseView provides many view-related features. While you can get view components by creating individual instances of yiibaseView or its child class, in most cases you will mainly use the view application component. You can configure this component in application configurations like the following: [ // ... ’components’ => [ ’view’ => [ ’class’ => ’appcomponentsView’, ], // ... ], ]
  • 103. 3.7. VIEWS 97 View components provide the following useful view-related features, each described in more details in a separate section: • theming: allows you to develop and change the theme for your Web site. • fragment caching: allows you to cache a fragment within a Web page. • client script handling: supports CSS and JavaScript registration and rendering. • asset bundle handling: supports registering and rendering of asset bundles. • alternative template engines: allows you to use other template engines, such as Twig14, Smarty15. You may also frequently use the following minor yet useful features when you are developing Web pages. Setting Page Titles Every Web page should have a title. Normally the title tag is being displayed in a layout. However, in practice the title is often determined in content views rather than layouts. To solve this problem, yiiwebView provides the yiiwebView::title property for you to pass the title information from content views to layouts. To make use of this feature, in each content view, you can set the page title like the following: <?php $this->title = ’My page title’; ?> Then in the layout, make sure you have the following code in the <head> section: <title><?= Html::encode($this->title) ?></title> Registering Meta Tags Web pages usually need to generate various meta tags needed by different parties. Like page titles, meta tags appear in the <head> section and are usually generated in layouts. If you want to specify what meta tags to generate in content views, you can call yiiwebView::registerMetaTag() in a content view, like the following: 14 http://twig.sensiolabs.org/ 15 http://www.smarty.net/
  • 104. 98 CHAPTER 3. APPLICATION STRUCTURE <?php $this->registerMetaTag([’name’ => ’keywords’, ’content’ => ’yii, framework, php’]); ?> The above code will register a “keywords” meta tag with the view component. The registered meta tag is rendered after the layout finishes rendering. By then, the following HTML code will be inserted at the place where you call yiiwebView::head() in the layout and generate the following HTML code: <meta name="keywords" content="yii, framework, php"> Note that if you call yiiwebView::registerMetaTag() multiple times, it will register multiple meta tags, regardless whether the meta tags are the same or not. To make sure there is only a single instance of a meta tag type, you can specify a key as a second parameter when calling the method. For example, the following code registers two “description” meta tags. However, only the second one will be rendered. $this->registerMetaTag([’name’ => ’description’, ’content’ => ’This is my cool website made with Yii!’], ’description’); $this->registerMetaTag([’name’ => ’description’, ’content’ => ’This website is about funny raccoons.’], ’description’); Registering Link Tags Like meta tags, link tags are useful in many cases, such as customizing favicon, pointing to RSS feed or delegating OpenID to another server. You can work with link tags in the similar way as meta tags by using yiiweb View::registerLinkTag(). For example, in a content view, you can re- gister a link tag like follows, $this->registerLinkTag([ ’title’ => ’Live News for Yii’, ’rel’ => ’alternate’, ’type’ => ’application/rss+xml’, ’href’ => ’http://www.yiiframework.com/rss.xml/’, ]); The code above will result in <link title="Live News for Yii" rel="alternate" type="application/rss+xml" href="http://www.yiiframework.com/rss.xml/"> Similar as yiiwebView::registerMetaTag(), you can specify a key when calling yiiwebView::registerLinkTag() to avoid generated repeated link tags.
  • 105. 3.7. VIEWS 99 3.7.5 View Events yiibaseView trigger several events during the view rendering process. You may respond to these events to inject content into views or process the rendering results before they are sent to end users. • yiibaseView::EVENT_BEFORE_RENDER: triggered at the beginning of rendering a file in a controller. Handlers of this event may set yiibase ViewEvent::isValid to be false to cancel the rendering process. • yiibaseView::EVENT_AFTER_RENDER: triggered by the call of yii baseView::beginPage() in layouts. Handlers of this event may ob- tain the rendering result through yiibaseViewEvent::output and may modify this property to change the rendering result. • yiibaseView::EVENT_BEGIN_PAGE: triggered by the call of yiibase View::beginPage() in layouts. • yiibaseView::EVENT_END_PAGE: triggered by the call of yiibase View::endPage() in layouts. • yiiwebView::EVENT_BEGIN_BODY: triggered by the call of yiiweb View::beginBody() in layouts. • yiiwebView::EVENT_END_BODY: triggered by the call of yiiwebView ::endBody() in layouts. For example, the following code injects the current date at the end of the page body: Yii::$app->view->on(View::EVENT_END_BODY, function () { echo date(’Y-m-d’); }); 3.7.6 Rendering Static Pages Static pages refer to those Web pages whose main content are mostly static without the need of accessing dynamic data pushed from controllers. You can output static pages by putting their code in the view, and then using the code like the following in a controller: public function actionAbout() { return $this->render(’about’); } If a Web site contains many static pages, it would be very tedious repeating the similar code many times. To solve this problem, you may introduce a standalone action called yiiwebViewAction in a controller. For example,
  • 106. 100 CHAPTER 3. APPLICATION STRUCTURE namespace appcontrollers; use yiiwebController; class SiteController extends Controller { public function actions() { return [ ’page’ => [ ’class’ => ’yiiwebViewAction’, ], ]; } } Now if you create a view named about under the directory @app/views/site/ pages, you will be able to display this view by the following URL: http://localhost/index.php?r=site/page&view=about The GET parameter view tells yiiwebViewAction which view is requested. The action will then look for this view under the directory @app/views/site/ pages. You may configure yiiwebViewAction::viewPrefix to change the directory for searching these views. 3.7.7 Best Practices Views are responsible for presenting models in the format that end users desire. In general, views • should mainly contain presentational code, such as HTML, and simple PHP code to traverse, format and render data. • should not contain code that performs DB queries. Such code should be done in models. • should avoid direct access to request data, such as $_GET, $_POST. This belongs to controllers. If request data is needed, they should be pushed into views by controllers. • may read model properties, but should not modify them. To make views more manageable, avoid creating views that are too complex or contain too much redundant code. You may use the following techniques to achieve this goal: • use layouts to represent common presentational sections (e.g. page header, footer).
  • 107. 3.8. MODULES 101 • divide a complicated view into several smaller ones. The smaller views can be rendered and assembled into a bigger one using the rendering methods that we have described. • create and use widgets as building blocks of views. • create and use helper classes to transform and format data in views. 3.8 Modules Modules are self-contained software units that consist of models, views, con- trollers, and other supporting components. End users can access the con- trollers of a module when it is installed in application. For these reasons, modules are often viewed as mini-applications. Modules differ from applic- ations in that modules cannot be deployed alone and must reside within applications. 3.8.1 Creating Modules A module is organized as a directory which is called the yiibaseModule ::basePath of the module. Within the directory, there are sub-directories, such as controllers, models, views, which hold controllers, models, views, and other code, just like in an application. The following example shows the content within a module: forum/ Module.php the module class file controllers/ containing controller class files DefaultController.php the default controller class file models/ containing model class files views/ containing controller view and layout files layouts/ containing layout view files default/ containing view files for DefaultController index.php the index view file Module Classes Each module should have a module class which extends from yiibase Module. The class should be located directly under the module’s yiibase Module::basePath and should be autoloadable. When a module is being accessed, a single instance of the corresponding module class will be cre- ated. Like application instances, module instances are used to share data and components for code within modules. The following is an example how a module class may look like:
  • 108. 102 CHAPTER 3. APPLICATION STRUCTURE namespace appmodulesforum; class Module extends yiibaseModule { public function init() { parent::init(); $this->params[’foo’] = ’bar’; // ... other initialization code ... } } If the init() method contains a lot of code initializing the module’s proper- ties, you may also save them in terms of a configuration and load it with the following code in init(): public function init() { parent::init(); // initialize the module with the configuration loaded from config.php Yii::configure($this, require(__DIR__ . ’/config.php’)); } where the configuration file config.php may contain the following content, similar to that in an application configuration. <?php return [ ’components’ => [ // list of component configurations ], ’params’ => [ // list of parameters ], ]; Controllers in Modules When creating controllers in a module, a convention is to put the control- ler classes under the controllers sub-namespace of the namespace of the module class. This also means the controller class files should be put in the controllers directory within the module’s yiibaseModule::basePath. For example, to create a post controller in the forum module shown in the last subsection, you should declare the controller class like the following: namespace appmodulesforumcontrollers; use yiiwebController; class PostController extends Controller { // ...
  • 109. 3.8. MODULES 103 } You may customize the namespace of controller classes by configuring the yiibaseModule::controllerNamespace property. In case when some of the controllers are out of this namespace, you may make them accessible by configuring the yiibaseModule::controllerMap property, similar to what you do in an application. Views in Modules Views in a module should be put in the views directory within the module’s yiibaseModule::basePath. For views rendered by a controller in the module, they should be put under the directory views/ControllerID, where ControllerID refers to the controller ID. For example, if the controller class is PostController, the directory would be views/post within the module’s yii baseModule::basePath. A module can specify a layout that is applied to the views rendered by the module’s controllers. The layout should be put in the views/layouts dir- ectory by default, and you should configure the yiibaseModule::layout property to point to the layout name. If you do not configure the layout property, the application’s layout will be used instead. 3.8.2 Using Modules To use a module in an application, simply configure the application by listing the module in the yiibaseApplication::modules property of the applic- ation. The following code in the application configuration uses the forum module: [ ’modules’ => [ ’forum’ => [ ’class’ => ’appmodulesforumModule’, // ... other configurations for the module ... ], ], ] The yiibaseApplication::modules property takes an array of module configurations. Each array key represents a module ID which uniquely identi- fies the module among all modules in the application, and the corresponding array value is a configuration for creating the module. Routes Like accessing controllers in an application, routes are used to address con- trollers in a module. A route for a controller within a module must begin
  • 110. 104 CHAPTER 3. APPLICATION STRUCTURE with the module ID followed by the controller ID and action ID. For ex- ample, if an application uses a module named forum, then the route forum/ post/index would represent the index action of the post controller in the mod- ule. If the route only contains the module ID, then the yiibaseModule ::defaultRoute property, which defaults to default, will determine which controller/action should be used. This means a route forum would represent the default controller in the forum module. Accessing Modules Within a module, you may often need to get the instance of the module class so that you can access the module ID, module parameters, module components, etc. You can do so by using the following statement: $module = MyModuleClass::getInstance(); where MyModuleClass refers to the name of the module class that you are interested in. The getInstance() method will return the currently requested instance of the module class. If the module is not requested, the method will return null. Note that You do not want to manually create a new instance of the module class because it will be different from the one created by Yii in response to a request. Info: When developing a module, you should not assume the module will use a fixed ID. This is because a module can be associated with an arbitrary ID when used in an application or within another module. In order to get the module ID, you should use the above approach to get the module instance first, and then get the ID via $module->id. You may also access the instance of a module using the following approaches: // get the module whose ID is "forum" $module = Yii::$app->getModule(’forum’); // get the module to which the currently requested controller belongs $module = Yii::$app->controller->module; The first approach is only useful when you know the module ID, while the second approach is best used when you know about the controllers being requested. Once getting hold of a module instance, you can access parameters or components registered with the module. For example, $maxPostCount = $module->params[’maxPostCount’];
  • 111. 3.8. MODULES 105 Bootstrapping Modules Some modules may need to be run for every request. The yiidebugModule module is such an example. To do so, list the IDs of such modules in the yiibaseApplication::bootstrap property of the application. For example, the following application configuration makes sure the debug module is always load: [ ’bootstrap’ => [ ’debug’, ], ’modules’ => [ ’debug’ => ’yiidebugModule’, ], ] 3.8.3 Nested Modules Modules can be nested in unlimited levels. That is, a module can contain another module which can contain yet another module. We call the former parent module while the latter child module. Child modules must be declared in the yiibaseModule::modules property of their parent modules. For example, namespace appmodulesforum; class Module extends yiibaseModule { public function init() { parent::init(); $this->modules = [ ’admin’ => [ // you should consider using a shorter namespace here! ’class’ => ’appmodulesforummodulesadminModule’, ], ]; } } For a controller within a nested module, its route should include the IDs of all its ancestor module. For example, the route forum/admin/dashboard/index represents the index action of the dashboard controller in the admin module which is a child module of the forum module.
  • 112. 106 CHAPTER 3. APPLICATION STRUCTURE 3.8.4 Best Practices Modules are best used in large applications whose features can be divided into several groups, each consisting of a set of closely related features. Each such feature group can be developed as a module which is developed and maintained by a specific developer or team. Modules are also a good way of reusing code at the feature group level. Some commonly used features, such as user management, comment manage- ment, can all be developed in terms of modules so that they can be reused easily in future projects. 3.9 Filters Filters are objects that run before and/or after controller actions. For ex- ample, an access control filter may run before actions to ensure that they are allowed to be accessed by particular end users; a content compression filter may run after actions to compress the response content before sending them out to end users. A filter may consist of a pre-filter (filtering logic applied before actions) and/or a post-filter (logic applied after actions). 3.9.1 Using Filters Filters are essentially a special kind of behaviors. Therefore, using filters is the same as using behaviors. You can declare filters in a controller class by overriding its yiibaseController::behaviors() method like the follow- ing: public function behaviors() { return [ [ ’class’ => ’yiifiltersHttpCache’, ’only’ => [’index’, ’view’], ’lastModified’ => function ($action, $params) { $q = new yiidbQuery(); return $q->from(’user’)->max(’updated_at’); }, ], ]; } By default, filters declared in a controller class will be applied to all actions in that controller. You can, however, explicitly specify which actions the filter should be applied to by configuring the yiibaseActionFilter:: only property. In the above example, the HttpCache filter only applies to the index and view actions. You can also configure the yiibaseActionFilter ::except property to blacklist some actions from being filtered.
  • 113. 3.9. FILTERS 107 Besides controllers, you can also declare filters in a module or applica- tion. When you do so, the filters will be applied to all controller actions belonging to that module or application, unless you configure the filters’ yii baseActionFilter::only and yiibaseActionFilter::except proper- ties like described above. Note: When declaring filters in modules or applications, you should use routes instead of action IDs in the yiibaseActionFilter ::only and yiibaseActionFilter::except properties. This is because action IDs alone cannot fully specify actions within the scope of a module or application. When multiple filters are configured for a single action, they are applied according to the rules described below, • Pre-filtering – Apply filters declared in the application in the order they are listed in behaviors(). – Apply filters declared in the module in the order they are listed in behaviors(). – Apply filters declared in the controller in the order they are listed in behaviors(). – If any of the filters cancel the action execution, the filters (both pre-filters and post-filters) after it will not be applied. • Running the action if it passes the pre-filtering. • Post-filtering – Apply filters declared in the controller in the reverse order they are listed in behaviors(). – Apply filters declared in the module in the reverse order they are listed in behaviors(). – Apply filters declared in the application in the reverse order they are listed in behaviors(). 3.9.2 Creating Filters To create a new action filter, extend from yiibaseActionFilter and override the yiibaseActionFilter::beforeAction() and/or yiibase ActionFilter::afterAction() methods. The former will be executed be- fore an action runs while the latter after an action runs. The return value of yiibaseActionFilter::beforeAction() determines whether an action
  • 114. 108 CHAPTER 3. APPLICATION STRUCTURE should be executed or not. If it is false, the filters after this one will be skipped and the action will not be executed. The following example shows a filter that logs the action execution time: namespace appcomponents; use Yii; use yiibaseActionFilter; class ActionTimeFilter extends ActionFilter { private $_startTime; public function beforeAction($action) { $this->_startTime = microtime(true); return parent::beforeAction($action); } public function afterAction($action, $result) { $time = microtime(true) - $this->_startTime; Yii::trace("Action ’{$action->uniqueId}’ spent $time second."); return parent::afterAction($action, $result); } } 3.9.3 Core Filters Yii provides a set of commonly used filters, found primarily under the yii filters namespace. In the following, we will briefly introduce these filters. yiifiltersAccessControl AccessControl provides simple access control based on a set of yiifilters AccessControl::rules. In particular, before an action is executed, Ac- cessControl will examine the listed rules and find the first one that matches the current context variables (such as user IP address, user login status, etc.) The matching rule will dictate whether to allow or deny the execution of the requested action. If no rule matches, the access will be denied. The following example shows how to allow authenticated users to access the create and update actions while denying all other users from accessing these two actions. use yiifiltersAccessControl; public function behaviors() { return [ ’access’ => [ ’class’ => AccessControl::className(),
  • 115. 3.9. FILTERS 109 ’only’ => [’create’, ’update’], ’rules’ => [ // allow authenticated users [ ’allow’ => true, ’roles’ => [’@’], ], // everything else is denied by default ], ], ]; } For more details about access control in general, please refer to the Author- ization section. Authentication Method Filters Authentication method filters are used to authenticate a user based using various methods, such as HTTP Basic Auth16, OAuth 217. These filter classes are all under the yiifiltersauth namespace. The following example shows how you can use yiifiltersauthHttpBasicAuth to authenticate a user using an access token based on HTTP Basic Auth method. Note that in order for this to work, your yiiwebUser::identityClass must implement the yiiwebIdentityInterface::findIdentityByAccessToken() method. use yiifiltersauthHttpBasicAuth; public function behaviors() { return [ ’basicAuth’ => [ ’class’ => HttpBasicAuth::className(), ], ]; } Authentication method filters are commonly used in implementing RESTful APIs. For more details, please refer to the RESTful Authentication section. yiifiltersContentNegotiator ContentNegotiator supports response format negotiation and application language negotiation. It will try to determine the response format and/or language by examining GET parameters and Accept HTTP header. 16 http://en.wikipedia.org/wiki/Basic_access_authentication 17 http://oauth.net/2/
  • 116. 110 CHAPTER 3. APPLICATION STRUCTURE In the following example, ContentNegotiator is configured to support JSON and XML response formats, and English (United States) and German languages. use yiifiltersContentNegotiator; use yiiwebResponse; public function behaviors() { return [ [ ’class’ => ContentNegotiator::className(), ’formats’ => [ ’application/json’ => Response::FORMAT_JSON, ’application/xml’ => Response::FORMAT_XML, ], ’languages’ => [ ’en-US’, ’de’, ], ], ]; } Response formats and languages often need to be determined much earlier during the application lifecycle. For this reason, ContentNegotiator is de- signed in a way such that it can also be used as a bootstrapping component besides filter. For example, you may configure it in the application config- uration like the following: use yiifiltersContentNegotiator; use yiiwebResponse; [ ’bootstrap’ => [ [ ’class’ => ContentNegotiator::className(), ’formats’ => [ ’application/json’ => Response::FORMAT_JSON, ’application/xml’ => Response::FORMAT_XML, ], ’languages’ => [ ’en-US’, ’de’, ], ], ], ]; Info: In case the preferred content type and language cannot be determined from a request, the first format and language listed in formats and languages will be used.
  • 117. 3.9. FILTERS 111 yiifiltersHttpCache HttpCache implements client-side caching by utilizing the Last-Modified and Etag HTTP headers. For example, use yiifiltersHttpCache; public function behaviors() { return [ [ ’class’ => HttpCache::className(), ’only’ => [’index’], ’lastModified’ => function ($action, $params) { $q = new yiidbQuery(); return $q->from(’user’)->max(’updated_at’); }, ], ]; } Please refer to the HTTP Caching section for more details about using Ht- tpCache. yiifiltersPageCache PageCache implements server-side caching of whole pages. In the following example, PageCache is applied to the index action to cache the whole page for maximum 60 seconds or until the count of entries in the post table changes. It also stores different versions of the page depending on the chosen application language. use yiifiltersPageCache; use yiicachingDbDependency; public function behaviors() { return [ ’pageCache’ => [ ’class’ => PageCache::className(), ’only’ => [’index’], ’duration’ => 60, ’dependency’ => [ ’class’ => DbDependency::className(), ’sql’ => ’SELECT COUNT(*) FROM post’, ], ’variations’ => [ Yii::$app->language, ] ], ]; } Please refer to the Page Caching section for more details about using PageCache.
  • 118. 112 CHAPTER 3. APPLICATION STRUCTURE yiifiltersRateLimiter RateLimiter implements a rate limiting algorithm based on the leaky bucket algorithm18. It is primarily used in implementing RESTful APIs. Please refer to the Rate Limiting section for details about using this filter. yiifiltersVerbFilter VerbFilter checks if the HTTP request methods are allowed by the requested actions. If not allowed, it will throw an HTTP 405 exception. In the following example, VerbFilter is declared to specify a typical set of allowed request methods for CRUD actions. use yiifiltersVerbFilter; public function behaviors() { return [ ’verbs’ => [ ’class’ => VerbFilter::className(), ’actions’ => [ ’index’ => [’get’], ’view’ => [’get’], ’create’ => [’get’, ’post’], ’update’ => [’get’, ’put’, ’post’], ’delete’ => [’post’, ’delete’], ], ], ]; } yiifiltersCors Cross-origin resource sharing CORS19 is a mechanism that allows many re- sources (e.g. fonts, JavaScript, etc.) on a Web page to be requested from another domain outside the domain the resource originated from. In particu- lar, JavaScript’s AJAX calls can use the XMLHttpRequest mechanism. Such “cross-domain” requests would otherwise be forbidden by Web browsers, per the same origin security policy. CORS defines a way in which the browser and the server can interact to determine whether or not to allow the cross- origin request. The yiifiltersCors should be defined before Authentication / Au- thorization filters to make sure the CORS headers will always be sent. use yiifiltersCors; use yiihelpersArrayHelper; 18 http://en.wikipedia.org/wiki/Leaky_bucket 19 https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS
  • 119. 3.9. FILTERS 113 public function behaviors() { return ArrayHelper::merge([ [ ’class’ => Cors::className(), ], ], parent::behaviors()); } The Cors filtering could be tuned using the cors property. • cors[’Origin’]: array used to define allowed origins. Can be [’*’ ] (everyone) or [’http://www.myserver.net’, ’http://www.myotherserver. com’]. Default to [’*’]. • cors[’Access-Control-Request-Method’]: array of allowed verbs like [’ GET’, ’OPTIONS’, ’HEAD’]. Default to [’GET’, ’POST’, ’PUT’, ’PATCH’, ’ DELETE’, ’HEAD’, ’OPTIONS’]. • cors[’Access-Control-Request-Headers’]: array of allowed headers. Can be [’*’] all headers or specific ones [’X-Request-With’]. Default to [’* ’]. • cors[’Access-Control-Allow-Credentials’]: define if current request can be made using credentials. Can be true, false or null (not set). Default to null. • cors[’Access-Control-Max-Age’]: define lifetime of pre-flight request. De- fault to 86400. For example, allowing CORS for origin : http://www.myserver.net with method GET, HEAD and OPTIONS : use yiifiltersCors; use yiihelpersArrayHelper; public function behaviors() { return ArrayHelper::merge([ [ ’class’ => Cors::className(), ’cors’ => [ ’Origin’ => [’http://www.myserver.net’], ’Access-Control-Request-Method’ => [’GET’, ’HEAD’, ’OPTIONS’ ], ], ], ], parent::behaviors()); } You may tune the CORS headers by overriding default parameters on a per action basis. For example adding the Access-Control-Allow-Credentials for the login action could be done like this :
  • 120. 114 CHAPTER 3. APPLICATION STRUCTURE use yiifiltersCors; use yiihelpersArrayHelper; public function behaviors() { return ArrayHelper::merge([ [ ’class’ => Cors::className(), ’cors’ => [ ’Origin’ => [’http://www.myserver.net’], ’Access-Control-Request-Method’ => [’GET’, ’HEAD’, ’OPTIONS’ ], ], ’actions’ => [ ’login’ => [ ’Access-Control-Allow-Credentials’ => true, ] ] ], ], parent::behaviors()); } 3.10 Widgets Widgets are reusable building blocks used in views to create complex and configurable user interface elements in an object-oriented fashion. For ex- ample, a date picker widget may generate a fancy date picker that allows users to pick a date as their input. All you need to do is just to insert the code in a view like the following: <?php use yiijuiDatePicker; ?> <?= DatePicker::widget([’name’ => ’date’]) ?> There are a good number of widgets bundled with Yii, such as yiiwidgets ActiveForm, yiiwidgetsMenu, jQuery UI widgets, Twitter Bootstrap widgets. In the following, we will introduce the basic knowledge about wid- gets. Please refer to the class API documentation if you want to learn about the usage of a particular widget. 3.10.1 Using Widgets Widgets are primarily used in views. You can call the yiibaseWidget:: widget() method to use a widget in a view. The method takes a configura- tion array for initializing the widget and returns the rendering result of the widget. For example, the following code inserts a date picker widget which is configured to use Russian language and keep the input in the from_date attribute of $model.
  • 121. 3.10. WIDGETS 115 <?php use yiijuiDatePicker; ?> <?= DatePicker::widget([ ’model’ => $model, ’attribute’ => ’from_date’, ’language’ => ’ru’, ’clientOptions’ => [ ’dateFormat’ => ’yy-mm-dd’, ], ]) ?> Some widgets can take a block of content which should be enclosed between the invocation of yiibaseWidget::begin() and yiibaseWidget::end(). For example, the following code uses the yiiwidgetsActiveForm widget to generate a login form. The widget will generate the opening and clos- ing <form> tags at the place where begin() and end() are called, respectively. Anything in between will be rendered as is. <?php use yiiwidgetsActiveForm; use yiihelpersHtml; ?> <?php $form = ActiveForm::begin([’id’ => ’login-form’]); ?> <?= $form->field($model, ’username’) ?> <?= $form->field($model, ’password’)->passwordInput() ?> <div class="form-group"> <?= Html::submitButton(’Login’) ?> </div> <?php ActiveForm::end(); ?> Note that unlike yiibaseWidget::widget() which returns the rendering result of a widget, the method yiibaseWidget::begin() returns an in- stance of the widget which you can use to build the widget content. 3.10.2 Creating Widgets To create a widget, extend from yiibaseWidget and override the yii baseWidget::init() and/or yiibaseWidget::run() methods. Usu- ally, the init() method should contain the code that normalizes the widget properties, while the run() method should contain the code that generates the rendering result of the widget. The rendering result may be directly “echoed” or returned as a string by run(). In the following example, HelloWidget HTML-encodes and displays the content assigned to its message property. If the property is not set, it will display “Hello World” by default.
  • 122. 116 CHAPTER 3. APPLICATION STRUCTURE namespace appcomponents; use yiibaseWidget; use yiihelpersHtml; class HelloWidget extends Widget { public $message; public function init() { parent::init(); if ($this->message === null) { $this->message = ’Hello World’; } } public function run() { return Html::encode($this->message); } } To use this widget, simply insert the following code in a view: <?php use appcomponentsHelloWidget; ?> <?= HelloWidget::widget([’message’ => ’Good morning’]) ?> Below is a variant of HelloWidget which takes the content enclosed within the begin() and end() calls, HTML-encodes it and then displays it. namespace appcomponents; use yiibaseWidget; use yiihelpersHtml; class HelloWidget extends Widget { public function init() { parent::init(); ob_start(); } public function run() { $content = ob_get_clean(); return Html::encode($content); } } As you can see, PHP output buffer is started in init() so that any output between the calls of init() and run() can be captured, processed and returned
  • 123. 3.10. WIDGETS 117 in run(). Info: When you call yiibaseWidget::begin(), a new instance of the widget will be created and the init() method will be called at the end of the widget constructor. When you call yiibase Widget::end(), the run() method will be called whose return result will be echoed by end(). The following code shows how to use this new variant of HelloWidget: <?php use appcomponentsHelloWidget; ?> <?php HelloWidget::begin(); ?> content that may contain <tag>’s <?php HelloWidget::end(); ?> Sometimes, a widget may need to render a big chunk of content. While you can embed the content within the run() method, a better approach is to put it in a view and call yiibaseWidget::render() to render it. For example, public function run() { return $this->render(’hello’); } By default, views for a widget should be stored in files in the WidgetPath /views directory, where WidgetPath stands for the directory containing the widget class file. Therefore, the above example will render the view file @app/components/views/hello.php, assuming the widget class is located under @app/components. You may override the yiibaseWidget::getViewPath() method to customize the directory containing the widget view files. 3.10.3 Best Practices Widgets are an object-oriented way of reusing view code. When creating widgets, you should still follow the MVC pattern. In general, you should keep logic in widget classes and keep presentation in views. Widgets should be designed to be self-contained. That is, when using a widget, you should be able to just drop it in a view without doing anything else. This could be tricky if a widget requires external resources, such as CSS, JavaScript, images, etc. Fortunately, Yii provides the support for asset bundles, which can be utilized to solve the problem. When a widget contains view code only, it is very similar to a view. In fact, in this case, their only difference is that a widget is a redistributable class, while a view is just a plain PHP script that you would prefer to keep it within your application.
  • 124. 118 CHAPTER 3. APPLICATION STRUCTURE 3.11 Assets An asset in Yii is a file that may be referenced in a Web page. It can be a CSS file, a JavaScript file, an image or video file, etc. Assets are located in Web-accessible directories and are directly served by Web servers. It is often preferable to manage assets programmatically. For example, when you use the yiijuiDatePicker widget in a page, it will automat- ically include the required CSS and JavaScript files, instead of asking you to manually find these files and include them. And when you upgrade the widget to a new version, it will automatically use the new version of the asset files. In this tutorial, we will describe the powerful asset management capability provided in Yii. 3.11.1 Asset Bundles Yii manages assets in the unit of asset bundle. An asset bundle is simply a collection of assets located in a directory. When you register an asset bundle in a view, it will include the CSS and JavaScript files in the bundle in the rendered Web page. 3.11.2 Defining Asset Bundles Asset bundles are specified as PHP classes extending from yiiwebAssetBundle. The name of a bundle is simply its corresponding PHP class name which should be autoloadable. In an asset bundle class, you would typically spe- cify where the assets are located, what CSS and JavaScript files the bundle contains, and how the bundle depends on other bundles. The following code defines the main asset bundle used by the basic ap- plication template: <?php namespace appassets; use yiiwebAssetBundle; class AppAsset extends AssetBundle { public $basePath = ’@webroot’; public $baseUrl = ’@web’; public $css = [ ’css/site.css’, ]; public $js = [ ]; public $depends = [ ’yiiwebYiiAsset’, ’yiibootstrapBootstrapAsset’, ];
  • 125. 3.11. ASSETS 119 } The above AppAsset class specifies that the asset files are located under the @webroot directory which is corresponding to the URL @web; the bundle con- tains a single CSS file css/site.css and no JavaScript file; the bundle depends on two other bundles: yiiwebYiiAsset and yiibootstrapBootstrapAsset. More detailed explanation about the properties of yiiwebAssetBundle can be found in the following: • yiiwebAssetBundle::sourcePath: specifies the root directory that contains the asset files in this bundle. This property should be set if the root directory is not Web accessible. Otherwise, you should set the yii webAssetBundle::basePath property and yiiwebAssetBundle:: baseUrl, instead. Path aliases can be used here. • yiiwebAssetBundle::basePath: specifies a Web-accessible direct- ory that contains the asset files in this bundle. When you specify the yiiwebAssetBundle::sourcePath property, the asset manager will publish the assets in this bundle to a Web-accessible directory and overwrite this property accordingly. You should set this property if your asset files are already in a Web-accessible directory and do not need asset publishing. Path aliases can be used here. • yiiwebAssetBundle::baseUrl: specifies the URL corresponding to the directory yiiwebAssetBundle::basePath. Like yiiwebAssetBundle ::basePath, if you specify the yiiwebAssetBundle::sourcePath property, the asset manager will publish the assets and overwrite this property accordingly. Path aliases can be used here. • yiiwebAssetBundle::js: an array listing the JavaScript files con- tained in this bundle. Note that only forward slash “/” should be used as directory separators. Each JavaScript file can be specified in one of the following two formats: – a relative path representing a local JavaScript file (e.g. js/main. js). The actual path of the file can be determined by prepending yiiwebAssetManager::basePath to the relative path, and the actual URL of the file can be determined by prepending yiiweb AssetManager::baseUrl to the relative path. – an absolute URL representing an external JavaScript file. For ex- ample, http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min .js or //ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js. • yiiwebAssetBundle::css: an array listing the CSS files contained in this bundle. The format of this array is the same as that of yiiweb AssetBundle::js.
  • 126. 120 CHAPTER 3. APPLICATION STRUCTURE • yiiwebAssetBundle::depends: an array listing the names of the asset bundles that this bundle depends on (to be explained shortly). • yiiwebAssetBundle::jsOptions: specifies the options that will be passed to the yiiwebView::registerJsFile() method when it is called to register every JavaScript file in this bundle. • yiiwebAssetBundle::cssOptions: specifies the options that will be passed to the yiiwebView::registerCssFile() method when it is called to register every CSS file in this bundle. • yiiwebAssetBundle::publishOptions: specifies the options that will be passed to the yiiwebAssetManager::publish() method when it is called to publish source asset files to a Web directory. This is only used if you specify the yiiwebAssetBundle::sourcePath property. Asset Locations Assets, based on their location, can be classified as: • source assets: the asset files are located together with PHP source code which cannot be directly accessed via Web. In order to use source assets in a page, they should be copied to a Web directory and turned into the so-called published assets. This process is called asset publishing which will be described in detail shortly. • published assets: the asset files are located in a Web directory and can thus be directly accessed via Web. • external assets: the asset files are located on a Web server that is different from the one hosting your Web application. When defining an asset bundle class, if you specify the yiiwebAssetBundle ::sourcePath property, it means any assets listed using relative paths will be considered as source assets. If you do not specify this property, it means those assets are published assets (you should therefore specify yiiwebAssetBundle ::basePath and yiiwebAssetBundle::baseUrl to let Yii know where they are located.) It is recommended that you place assets belonging to an application in a Web directory to avoid the unnecessary asset publishing process. This is why AppAsset in the prior example specifies yiiwebAssetBundle::basePath in- stead of yiiwebAssetBundle::sourcePath. For extensions, because their assets are located together with their source code in directories that are not Web accessible, you have to specify the yiiwebAssetBundle::sourcePath property when defining asset bundle classes for them.
  • 127. 3.11. ASSETS 121 Note: Do not use @webroot/assets as the yiiwebAssetBundle ::sourcePath. This directory is used by default by the yii webAssetManager to save the asset files published from their source location. Any content in this directory are considered temporarily and may be subject to removal. Asset Dependencies When you include multiple CSS or JavaScript files in a Web page, they have to follow certain orders to avoid overriding issues. For example, if you are using a jQuery UI widget in a Web page, you have to make sure the jQuery JavaScript file is included before the jQuery UI JavaScript file. We call such ordering the dependencies among assets. Asset dependencies are mainly specified through the yiiwebAssetBundle ::depends property. In the AppAsset example, the asset bundle depends on two other asset bundles: yiiwebYiiAsset and yiibootstrapBootstrapAsset, which means the CSS and JavaScript files in AppAsset will be included after those files in the two dependent bundles. Asset dependencies are transitive. This means if bundle A depends on B which depends on C, A will depend on C, too. Asset Options You can specify the yiiwebAssetBundle::cssOptions and yiiwebAssetBundle ::jsOptions properties to customize the way that CSS and JavaScript files are included in a page. The values of these properties will be passed to the yiiwebView::registerCssFile() and yiiwebView::registerJsFile() methods, respectively, when they are called by the view to include CSS and JavaScript files. Note: The options you set in a bundle class apply to every CSS/- JavaScript file in the bundle. If you want to use different options for different files, you should create separate asset bundles, and use one set of options in each bundle. For example, to conditionally include a CSS file for browsers that are IE9 or above, you can use the following option: public $cssOptions = [’condition’ => ’lte IE9’]; This will cause a CSS file in the bundle to be included using the following HTML tags: <!--[if lte IE9]> <link rel="stylesheet" href="path/to/foo.css"> <![endif]-->
  • 128. 122 CHAPTER 3. APPLICATION STRUCTURE To include a JavaScript file in the head section of a page (by default, JavaS- cript files are included at the end of the body section), use the following option: public $jsOptions = [’position’ => yiiwebView::POS_HEAD]; Bower and NPM Assets Most JavaScript/CSS package are managed by Bower20 and/or NPM21. If your application or extension is using such a package, it is recommended that you follow these steps to manage the assets in the library: 1. Modify the composer.json file of your application or extension and list the package in the require entry. You should use bower-asset/PackageName (for Bower packages) or npm-asset/PackageName (for NPM packages) to refer to the library. 2. Create an asset bundle class and list the JavaScript/CSS files that you plan to use in your application or extension. You should specify the yiiwebAssetBundle::sourcePath property as @bower/PackageName or @npm/PackageName. This is because Composer will install the Bower or NPM package in the directory corresponding to this alias. Note: Some packages may put all their distributed files in a sub- directory. If this is the case, you should specify the subdirect- ory as the value of yiiwebAssetBundle::sourcePath. For example, yiiwebJqueryAsset uses @bower/jquery/dist instead of @bower/jquery. 3.11.3 Using Asset Bundles To use an asset bundle, register it with a view by calling the yiiweb AssetBundle::register() method. For example, in a view template you can register an asset bundle like the following: use appassetsAppAsset; AppAsset::register($this); // $this represents the view object If you are registering an asset bundle in other places, you should provide the needed view object. For example, to register an asset bundle in a widget class, you can get the view object by $this->view. When an asset bundle is registered with a view, behind the scene Yii will register all its dependent asset bundles. And if an asset bundle is located in a directory inaccessible through the Web, it will be published to a Web direct- ory. Later when the view renders a page, it will generate <link> and <script> 20 http://bower.io/ 21 https://www.npmjs.org/
  • 129. 3.11. ASSETS 123 tags for the CSS and JavaScript files listed in the registered bundles. The order of these tags is determined by the dependencies among the registered bundles and the order of the assets listed in the yiiwebAssetBundle:: css and yiiwebAssetBundle::js properties. Customizing Asset Bundles Yii manages asset bundles through an application component named assetManager which is implemented by yiiwebAssetManager. By configuring the yii webAssetManager::bundles property, it is possible to customize the be- havior of an asset bundle. For example, the default yiiwebJqueryAsset asset bundle uses the jquery.js file from the installed jquery Bower package. To improve the availability and performance, you may want to use a version hosted by Google. This can be achieved by configuring assetManager in the application configuration like the following: return [ // ... ’components’ => [ ’assetManager’ => [ ’bundles’ => [ ’yiiwebJqueryAsset’ => [ ’sourcePath’ => null, // do not publish the bundle ’js’ => [ ’//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery .min.js’, ] ], ], ], ], ]; You can configure multiple asset bundles similarly through yiiwebAssetManager ::bundles. The array keys should be the class names (without the leading backslash) of the asset bundles, and the array values should be the corres- ponding configuration arrays. Tip: You can conditionally choose which assets to use in an asset bundle. The following example shows how to use jquery.js in the development environment and jquery.min.js otherwise: ’yiiwebJqueryAsset’ => [ ’js’ => [ YII_ENV_DEV ? ’jquery.js’ : ’jquery.min.js’ ] ], You can disable one or multiple asset bundles by associating false with the names of the asset bundles that you want to disable. When you register
  • 130. 124 CHAPTER 3. APPLICATION STRUCTURE a disabled asset bundle with a view, none of its dependent bundles will be registered, and the view will also not include any of the assets in the bundle in the page it renders. For example, to disable yiiwebJqueryAsset, you can use the following configuration: return [ // ... ’components’ => [ ’assetManager’ => [ ’bundles’ => [ ’yiiwebJqueryAsset’ => false, ], ], ], ]; You can also disable all asset bundles by setting yiiwebAssetManager:: bundles as false. Asset Mapping Sometimes you want to “fix” incorrect/incompatible asset file paths used in multiple asset bundles. For example, bundle A uses jquery.min.js of version 1.11.1, and bundle B uses jquery.js of version 2.1.1. While you can fix the problem by customizing each bundle, an easier way is to use the asset map feature to map incorrect assets to the desired ones. To do so, configure the yiiwebAssetManager::assetMap property like the following: return [ // ... ’components’ => [ ’assetManager’ => [ ’assetMap’ => [ ’jquery.js’ => ’//ajax.googleapis.com/ajax/libs/jquery /2.1.1/jquery.min.js’, ], ], ], ]; The keys of yiiwebAssetManager::assetMap are the asset names that you want to fix, and the values are the desired asset paths. When you register an asset bundle with a view, each relative asset file in its yiiwebAssetBundle ::css and yiiwebAssetBundle::js arrays will be examined against this map. If any of the keys is found to be the last part of an asset file (which is prefixed with yiiwebAssetBundle::sourcePath if available), the cor- responding value will replace the asset and be registered with the view. For example, an asset file my/path/to/jquery.js matches a key jquery.js. Note: Only assets specified using relative paths are subject to asset mapping. And the target asset paths should be either
  • 131. 3.11. ASSETS 125 absolute URLs or paths relative to yiiwebAssetManager:: basePath. Asset Publishing As aforementioned, if an asset bundle is located in a directory that is not Web accessible, its assets will be copied to a Web directory when the bundle is being registered with a view. This process is called asset publishing, and is done automatically by the yiiwebAssetManager. By default, assets are published to the directory @webroot/assets which corresponds to the URL @web/assets. You may customize this location by con- figuring the yiiwebAssetManager::basePath and yiiwebAssetManager ::baseUrl properties. Instead of publishing assets by file copying, you may consider using sym- bolic links, if your OS and Web server allow. This feature can be enabled by setting yiiwebAssetManager::linkAssets to be true. return [ // ... ’components’ => [ ’assetManager’ => [ ’linkAssets’ => true, ], ], ]; With the above configuration, the asset manager will create a symbolic link to the source path of an asset bundle when it is being published. This is faster than file copying and can also ensure that the published assets are always up-to-date. 3.11.4 Commonly Used Asset Bundles The core Yii code has defined many asset bundles. Among them, the follow- ing bundles are commonly used and may be referenced in your application or extension code. • yiiwebYiiAsset: It mainly includes the yii.js file which implements a mechanism of organizing JavaScript code in modules. It also provides special support for data-method and data-confirm attributes and other useful features. • yiiwebJqueryAsset: It includes the jquery.js file from the jQuery bower package. • yiibootstrapBootstrapAsset: It includes the CSS file from the Twitter Bootstrap framework.
  • 132. 126 CHAPTER 3. APPLICATION STRUCTURE • yiibootstrapBootstrapPluginAsset: It includes the JavaScript file from the Twitter Bootstrap framework for supporting Bootstrap JavaScript plugins. • yiijuiJuiAsset: It includes the CSS and JavaScript files from the jQuery UI library. If your code depends on jQuery, jQuery UI or Bootstrap, you should use these predefined asset bundles rather than creating your own versions. If the default setting of these bundles do not satisfy your needs, you may customize them as described in the Customizing Asset Bundle subsection. 3.11.5 Asset Conversion Instead of directly writing CSS and/or JavaScript code, developers often write them in some extended syntax and use special tools to convert it into CSS/JavaScript. For example, for CSS code you may use LESS22 or SCSS23; and for JavaScript you may use TypeScript24. You can list the asset files in extended syntax in yiiwebAssetBundle ::css and yiiwebAssetBundle::js in an asset bundle. For example, class AppAsset extends AssetBundle { public $basePath = ’@webroot’; public $baseUrl = ’@web’; public $css = [ ’css/site.less’, ]; public $js = [ ’js/site.ts’, ]; public $depends = [ ’yiiwebYiiAsset’, ’yiibootstrapBootstrapAsset’, ]; } When you register such an asset bundle with a view, the yiiwebAssetManager will automatically run the pre-processor tools to convert assets in recognized extended syntax into CSS/JavaScript. When the view finally renders a page, it will include the CSS/JavaScript files in the page, instead of the original assets in extended syntax. Yii uses the file name extensions to identify which extended syntax an asset is in. By default it recognizes the following syntax and file name ex- tensions: 22 http://lesscss.org/ 23 http://sass-lang.com/ 24 http://www.typescriptlang.org/
  • 133. 3.11. ASSETS 127 • LESS25: .less • SCSS26: .scss • Stylus27: .styl • CoffeeScript28: .coffee • TypeScript29: .ts Yii relies on the installed pre-processor tools to convert assets. For example, o use LESS30 you should install the lessc pre-processor command. You can customize the pre-processor commands and the supported ex- tended syntax by configuring yiiwebAssetManager::converter like the following: return [ ’components’ => [ ’assetManager’ => [ ’converter’ => [ ’class’ => ’yiiwebAssetConverter’, ’commands’ => [ ’less’ => [’css’, ’lessc {from} {to} --no-color’], ’ts’ => [’js’, ’tsc --out {to} {from}’], ], ], ], ], ]; In the above we specify the supported extended syntax via the yiiweb AssetConverter::commands property. The array keys are the file extension names (without leading dot), and the array values are the resulting asset file extension names and the commands for performing the asset conversion. The tokens {from} and {to} in the commands will be replaced with the source asset file paths and the target asset file paths. Info: There are other ways of working with assets in extended syntax, besides the one described above. For example, you can use build tools such as grunt31 to monitor and automatically convert assets in extended syntax. In this case, you should list the resulting CSS/JavaScript files in asset bundles rather than the original files. 25 http://lesscss.org/ 26 http://sass-lang.com/ 27 http://learnboost.github.io/stylus/ 28 http://coffeescript.org/ 29 http://www.typescriptlang.org/ 30 http://lesscss.org/ 31 http://gruntjs.com/
  • 134. 128 CHAPTER 3. APPLICATION STRUCTURE 3.11.6 Combining and Compressing Assets A Web page can include many CSS and/or JavaScript files. To reduce the number of HTTP requests and the overall download size of these files, a common practice is to combine and compress multiple CSS/JavaScript files into one or very few files, and then include these compressed files instead of the original ones in the Web pages. Info: Combining and compressing assets is usually needed when an application is in production mode. In development mode, using the original CSS/JavaScript files is often more convenient for debugging purpose. In the following, we introduce an approach to combine and compress asset files without the need of modifying your existing application code. 1. Find out all asset bundles in your application that you plan to combine and compress. 2. Divide these bundles into one or a few groups. Note that each bundle can only belong to a single group. 3. Combine/compress the CSS files in each group into a single file. Do this similarly for the JavaScript files. 4. Define a new asset bundle for each group: • Set the yiiwebAssetBundle::css and yiiwebAssetBundle ::js properties to be the combined CSS and JavaScript files, respectively. • Customize the asset bundles in each group by setting their yii webAssetBundle::css and yiiwebAssetBundle::js prop- erties to be empty, and setting their yiiwebAssetBundle:: depends property to be the new asset bundle created for the group. Using this approach, when you register an asset bundle in a view, it causes the automatic registration of the new asset bundle for the group that the original bundle belongs to. And as a result, the combined/compressed asset files are included in the page, instead of the original ones. An Example Let’s use an example to further explain the above approach. Assume your application has two pages X and Y. Page X uses asset bundle A, B and C, while Page Y uses asset bundle B, C and D.
  • 135. 3.11. ASSETS 129 You have two ways to divide these asset bundles. One is to use a single group to include all asset bundles, the other is to put (A, B, C) in Group X, and (B, C, D) in Group Y. Which one is better? It depends. The first way has the advantage that both pages share the same combined CSS and JavaScript files, which makes HTTP caching more effective. On the other hand, because the single group contains all bundles, the size of the combined CSS and JavaScript files will be bigger and thus increase the initial file transmission time. In this example, we will use the first way, i.e., use a single group to contain all bundles. Info: Dividing asset bundles into groups is not trivial task. It usually requires analysis about the real world traffic data of vari- ous assets on different pages. At the beginning, you may start with a single group for simplicity. Use existing tools (e.g. Closure Compiler32, YUI Compressor33) to combine and compress CSS and JavaScript files in all the bundles. Note that the files should be combined in the order that satisfies the dependencies among the bundles. For example, if Bundle A depends on B which depends on both C and D, then you should list the asset files starting from C and D, followed by B and finally A. After combining and compressing, we get one CSS file and one JavaScript file. Assume they are named as all-xyz.css and all-xyz.js, where xyz stands for a timestamp or a hash that is used to make the file name unique to avoid HTTP caching problem. We are at the last step now. Configure the yiiwebAssetManager as follows in the application configuration: return [ ’components’ => [ ’assetManager’ => [ ’bundles’ => [ ’all’ => [ ’class’ => ’yiiwebAssetBundle’, ’basePath’ => ’@webroot/assets’, ’baseUrl’ => ’@web/assets’, ’css’ => [’all-xyz.css’], ’js’ => [’all-xyz.js’], ], ’A’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]], ’B’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]], ’C’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]], ’D’ => [’css’ => [], ’js’ => [], ’depends’ => [’all’]], ], ], ], ]; 32 https://developers.google.com/closure/compiler/ 33 https://github.com/yui/yuicompressor/
  • 136. 130 CHAPTER 3. APPLICATION STRUCTURE As explained in the Customizing Asset Bundles subsection, the above config- uration changes the default behavior of each bundle. In particular, Bundle A, B, C and D no longer have any asset files. They now all depend on the all bundle which contains the combined all-xyz.css and all-xyz.js files. Consequently, for Page X, instead of including the original source files from Bundle A, B and C, only these two combined files will be included; the same thing happens to Page Y. There is one final trick to make the above approach work more smoothly. Instead of directly modifying the application configuration file, you may put the bundle customization array in a separate file and conditionally include this file in the application configuration. For example, return [ ’components’ => [ ’assetManager’ => [ ’bundles’ => require(__DIR__ . ’/’ . (YII_ENV_PROD ? ’assets- prod.php’ : ’assets-dev.php’)), ], ], ]; That is, the asset bundle configuration array is saved in assets-prod.php for production mode, and assets-dev.php for non-production mode. Using the asset Command Yii provides a console command named asset to automate the approach that we just described. To use this command, you should first create a configuration file to describe what asset bundles should be combined and how they should be grouped. You can use the asset/template sub-command to generate a tem- plate first and then modify it to fit for your needs. yii asset/template assets.php The command generates a file named assets.php in the current directory. The content of this file looks like the following: <?php /** * Configuration file for the "yii asset" console command. * Note that in the console environment, some path aliases like ’@webroot’ and ’@web’ may not exist. * Please define these missing path aliases. */ return [ // Adjust command/callback for JavaScript files compressing: ’jsCompressor’ => ’java -jar compiler.jar --js {from} --js_output_file { to}’, // Adjust command/callback for CSS files compressing: ’cssCompressor’ => ’java -jar yuicompressor.jar --type css {from} -o {to }’,
  • 137. 3.11. ASSETS 131 // The list of asset bundles to compress: ’bundles’ => [ // ’yiiwebYiiAsset’, // ’yiiwebJqueryAsset’, ], // Asset bundle for compression output: ’targets’ => [ ’all’ => [ ’class’ => ’yiiwebAssetBundle’, ’basePath’ => ’@webroot/assets’, ’baseUrl’ => ’@web/assets’, ’js’ => ’js/all-{hash}.js’, ’css’ => ’css/all-{hash}.css’, ], ], // Asset manager configuration: ’assetManager’ => [ ], ]; You should modify this file and specify which bundles you plan to combine in the bundles option. In the targets option you should specify how the bundles should be divided into groups. You can specify one or multiple groups, as aforementioned. Note: Because the alias @webroot and @web are not available in the console application, you should explicitly define them in the configuration. JavaScript files are combined, compressed and written to js/all-{hash}.js where {hash} is replaced with the hash of the resulting file. The jsCompressor and cssCompressor options specify the console commands or PHP callbacks for performing JavaScript and CSS combining/compress- ing. By default Yii uses Closure Compiler34 for combining JavaScript files and YUI Compressor35 for combining CSS files. You should install tools manually or adjust these options to use your favorite tools. With the configuration file, you can run the asset command to combine and compress the asset files and then generate a new asset bundle configur- ation file assets-prod.php: yii asset assets.php config/assets-prod.php The generated configuration file can be included in the application configur- ation, like described in the last subsection. Info: Using the asset command is not the only option to automate the asset combining and compressing process. You can use the excellent task runner tool grunt36 to achieve the same goal. 34 https://developers.google.com/closure/compiler/ 35 https://github.com/yui/yuicompressor/ 36 http://gruntjs.com/
  • 138. 132 CHAPTER 3. APPLICATION STRUCTURE 3.12 Extensions Extensions are redistributable software packages specifically designed to be used in Yii applications and provide ready-to-use features. For example, the yiisoft/yii2-debug extension adds a handy debug toolbar at the bottom of every page in your application to help you more easily grasp how the pages are generated. You can use extensions to accelerate your development process. You can also package your code as extensions to share with other people your great work. Info: We use the term “extension” to refer to Yii-specific software packages. For general purpose software packages that can be used without Yii, we will refer to them using the term “package” or “library”. 3.12.1 Using Extensions To use an extension, you need to install it first. Most extensions are distrib- uted as Composer37 packages which can be installed by taking the following two simple steps: 1. modify the composer.json file of your application and specify which ex- tensions (Composer packages) you want to install. 2. run php composer.phar install to install the specified extensions. Note that you may need to install Composer38 if you do not have it. By default, Composer installs packages registered on Packagist39 - the biggest repository for open source Composer packages. You can look for extensions on Packagist. You may also create your own repository40 and configure Composer to use it. This is useful if you are developing closed open extensions and want to share within your projects. Extensions installed by Composer are stored in the BasePath/vendor direct- ory, where BasePath refers to the application’s base path. Because Composer is a dependency manager, when it installs a package, it will also install all its dependent packages. For example, to install the yiisoft/yii2-imagine extension, modify your composer.json like the following: { // ... 37 https://getcomposer.org/ 38 https://getcomposer.org/ 39 https://packagist.org/ 40 https://getcomposer.org/doc/05-repositories.md#repository
  • 139. 3.12. EXTENSIONS 133 "require": { // ... other dependencies "yiisoft/yii2-imagine": "*" } } After the installation, you should see the directory yiisoft/yii2-imagine under BasePath/vendor. You should also see another directory imagine/imagine which contains the installed dependent package. Info: The yiisoft/yii2-imagine is a core extension developed and maintained by the Yii developer team. All core extensions are hosted on Packagist41 and named like yiisoft/yii2-xyz, where xyz varies for different extensions. Now you can use the installed extensions like they are part of your applic- ation. The following example shows how you can use the yiiimagineImage class provided by the yiisoft/yii2-imagine extension: use Yii; use yiiimagineImage; // generate a thumbnail image Image::thumbnail(’@webroot/img/test-image.jpg’, 120, 120) ->save(Yii::getAlias(’@runtime/thumb-test-image.jpg’), [’quality’ => 50]); Info: Extension classes are autoloaded by the Yii class auto- loader. Installing Extensions Manually In some rare occasions, you may want to install some or all extensions manu- ally, rather than relying on Composer. To do so, you should 1. download the extension archive files and unpack them in the vendor directory. 2. install the class autoloaders provided by the extensions, if any. 3. download and install all dependent extensions as instructed. If an extension does not have a class autoloader but follows the PSR-4 stand- ard42, you may use the class autoloader provided by Yii to autoload the extension classes. All you need to do is just to declare a root alias for the 41 https://packagist.org/ 42 http://www.php-fig.org/psr/psr-4/
  • 140. 134 CHAPTER 3. APPLICATION STRUCTURE extension root directory. For example, assuming you have installed an ex- tension in the directory vendor/mycompany/myext, and the extension classes are under the myext namespace, then you can include the following code in your application configuration: [ ’aliases’ => [ ’@myext’ => ’@vendor/mycompany/myext’, ], ] 3.12.2 Creating Extensions You may consider creating an extension when you feel the need to share with other people your great code. An extension can contain any code you like, such as a helper class, a widget, a module, etc. It is recommended that you create an extension in terms of a Composer package43 so that it can be more easily installed and used by other users, liked described in the last subsection. Below are the basic steps you may follow to create an extension as a Composer package. 1. Create a project for your extension and host it on a VCS repository, such as github.com44. The development and maintenance work about the extension should be done on this repository. 2. Under the root directory of the project, create a file named composer. json as required by Composer. Please refer to the next subsection for more details. 3. Register your extension with a Composer repository, such as Pack- agist45, so that other users can find and install your extension using Composer. composer.json Each Composer package must have a composer.json file in its root directory. The file contains the metadata about the package. You may find complete specification about this file in the Composer Manual46. The following ex- ample shows the composer.json file for the yiisoft/yii2-imagine extension: { // package name "name": "yiisoft/yii2-imagine", 43 https://getcomposer.org/ 44 https://github.com 45 https://packagist.org/ 46 https://getcomposer.org/doc/01-basic-usage.md#composer-json-project-setup
  • 141. 3.12. EXTENSIONS 135 // package type "type": "yii2-extension", "description": "The Imagine integration for the Yii framework", "keywords": ["yii2", "imagine", "image", "helper"], "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3 Aimagine", "forum": "http://www.yiiframework.com/forum/", "wiki": "http://www.yiiframework.com/wiki/", "irc": "irc://irc.freenode.net/yii", "source": "https://github.com/yiisoft/yii2" }, "authors": [ { "name": "Antonio Ramirez", "email": "[email protected]" } ], // package dependencies "require": { "yiisoft/yii2": "*", "imagine/imagine": "v0.5.0" }, // class autoloading specs "autoload": { "psr-4": { "yiiimagine": "" } } } Package Name Each Composer package should have a package name which uniquely identifies the package among all others. The format of pack- age names is vendorName/projectName. For example, in the package name yiisoft/yii2-imagine, the vendor name and the project name are yiisoft and yii2-imagine, respectively. Do NOT use yiisoft as vendor name as it is reserved for use by the Yii core code. We recommend you prefix yii2- to the project name for packages rep- resenting Yii 2 extensions, for example, myname/yii2-mywidget. This will allow users to more easily tell whether a package is a Yii 2 extension. Package Type It is important that you specify the package type of your extension as yii2-extension so that the package can be recognized as a Yii extension when being installed.
  • 142. 136 CHAPTER 3. APPLICATION STRUCTURE When a user runs php composer.phar install to install an extension, the file vendor/yiisoft/extensions.php will be automatically updated to include the information about the new extension. From this file, Yii applications can know which extensions are installed (the information can be accessed via yiibaseApplication::extensions. Dependencies Your extension depends on Yii (of course). So you should list it (yiisoft/yii2) in the require entry in composer.json. If your extension also depends on other extensions or third-party libraries, you should list them as well. Make sure you also list appropriate version constraints (e.g. 1.*, @stable) for each dependent package. Use stable dependencies when your extension is released in a stable version. Most JavaScript/CSS packages are managed using Bower47 and/or NPM48, instead of Composer. Yii uses the Composer asset plugin49 to enable man- aging these kinds of packages through Composer. If your extension depends on a Bower package, you can simply list the dependency in composer.json like the following: { // package dependencies "require": { "bower-asset/jquery": ">=1.11.*" } } The above code states that the extension depends on the jquery Bower pack- age. In general, you can use bower-asset/PackageName to refer to a Bower pack- age in composer.json, and use npm-asset/PackageName to refer to a NPM package. When Composer installs a Bower or NPM package, by default the package content will be installed under the @vendor/bower/PackageName and @vendor/npm /Packages directories, respectively. These two directories can also be referred to using the shorter aliases @bower/PackageName and @npm/PackageName. For more details about asset management, please refer to the Assets section. Class Autoloading In order for your classes to be autoloaded by the Yii class autoloader or the Composer class autoloader, you should specify the autoload entry in the composer.json file, like shown below: { // .... "autoload": { "psr-4": { 47 http://bower.io/ 48 https://www.npmjs.org/ 49 https://github.com/francoispluchino/composer-asset-plugin
  • 143. 3.12. EXTENSIONS 137 "yiiimagine": "" } } } You may list one or multiple root namespaces and their corresponding file paths. When the extension is installed in an application, Yii will create for each listed root namespace an alias that refers to the directory corresponding to the namespace. For example, the above autoload declaration will correspond to an alias named @yii/imagine. Recommended Practices Because extensions are meant to be used by other people, you often need to take extra development effort. Below we introduce some common and recommended practices in creating high quality extensions. Namespaces To avoid name collisions and make the classes in your ex- tension autoloadable, you should use namespaces and name the classes in your extension by following the PSR-4 standard50 or PSR-0 standard51. You class namespaces should start with vendorNameextensionName, where extensionName is similar to the project name in the package name except that it should not contain the yii2- prefix. For example, for the yiisoft/yii2- imagine extension, we use yiiimagine as the namespace its classes. Do not use yii, yii2 or yiisoft as vendor name. These names are reserved for use by the Yii core code. Bootstrapping Classes Sometimes, you may want your extension to execute some code during the bootstrapping process stage of an applica- tion. For example, your extension may want to respond to the application’s beginRequest event to adjust some environment settings. While you can in- struct users of the extension to explicitly attach your event handler in the extension to the beginRequest event, a better way is to do this automatically. To achieve this goal, you can create a so-called bootstrapping class by implementing yiibaseBootstrapInterface. For example, namespace mynamemywidget; use yiibaseBootstrapInterface; use yiibaseApplication; class MyBootstrapClass implements BootstrapInterface { public function bootstrap($app) 50 http://www.php-fig.org/psr/psr-4/ 51 http://www.php-fig.org/psr/psr-0/
  • 144. 138 CHAPTER 3. APPLICATION STRUCTURE { $app->on(Application::EVENT_BEFORE_REQUEST, function () { // do something here }); } } You then list this class in the composer.json file of your extension like follows, { // ... "extra": { "bootstrap": "mynamemywidgetMyBootstrapClass" } } When the extension is installed in an application, Yii will automatically in- stantiate the bootstrapping class and call its yiibaseBootstrapInterface ::bootstrap() method during the bootstrapping process for every request. Working with Databases Your extension may need to access databases. Do not assume that the applications that use your extension will always use Yii::$db as the DB connection. Instead, you should declare a db property for the classes that require DB access. The property will allow users of your extension to customize which DB connection they would like your extension to use. As an example, you may refer to the yiicachingDbCache class and see how it declares and uses the db property. If your extension needs to create specific DB tables or make changes to DB schema, you should • provide migrations to manipulate DB schema, rather than using plain SQL files; • try to make the migrations applicable to different DBMS; • avoid using Active Record in the migrations. Using Assets If your extension is a widget or a module, chances are that it may require some assets to work. For example, a module may display some pages which contain images, JavaScript, and CSS. Because the files of an extension are all under the same directory which is not Web accessible when installed in an application, you have two choices to make the asset files directly accessible via Web: • ask users of the extension to manually copy the asset files to a specific Web-accessible folder;
  • 145. 3.12. EXTENSIONS 139 • declare an asset bundle and rely on the asset publishing mechanism to automatically copy the files listed in the asset bundle to a Web- accessible folder. We recommend you use the second approach so that your extension can be more easily used by other people. Please refer to the [Assets] section for more details about how to work with assets in general. Internationalization and Localization Your extension may be used by applications supporting different languages! Therefore, if your extension displays content to end users, you should try to internationalize and localize it. In particular, • If the extension displays messages intended for end users, the messages should be wrapped into Yii::t() so that they can be translated. Mes- sages meant for developers (such as internal exception messages) do not need to be translated. • If the extension displays numbers, dates, etc., they should be formatted using yiii18nFormatter with appropriate formatting rules. For more details, please refer to the Internationalization section. Testing You want your extension to run flawlessly without bringing prob- lems to other people. To reach this goal, you should test your extension before releasing it to public. It is recommended that you create various test cases to cover your exten- sion code rather than relying on manual tests. Each time before you release a new version of your extension, you may simply run these test cases to make sure everything is in good shape. Yii provides testing support, which can help you to more easily write unit tests, acceptance tests and functionality tests. For more details, please refer to the Testing section. Versioning You should give each release of your extension a version num- ber (e.g. 1.0.1). We recommend you follow the semantic versioning52 prac- tice when determining what version numbers should be used. Releasing To let other people know your extension, you need to release it to public. If it is the first time you release an extension, you should register it on a Composer repository, such as Packagist53. After that, all you need to do is simply creating a release tag (e.g. v1.0.1) on the VCS repository of your 52 http://semver.org 53 https://packagist.org/
  • 146. 140 CHAPTER 3. APPLICATION STRUCTURE extension and notify the Composer repository about the new release. People will then be able to find the new release, and install or update the extension through the Composer repository. In the releases of your extension, besides code files you should also con- sider including the followings to help other people learn about and use your extension: • A readme file in the package root directory: it describes what your extension does and how to install and use it. We recommend you write it in Markdown54 format and name the file as readme.md. • A changelog file in the package root directory: it lists what changes are made in each release. The file may be written in Markdown format and named as changelog.md. • An upgrade file in the package root directory: it gives the instructions on how to upgrade from older releases of the extension. The file may be written in Markdown format and named as upgrade.md. • Tutorials, demos, screenshots, etc.: these are needed if your extension provides many features that cannot be fully covered in the readme file. • API documentation: your code should be well documented to allow other people more easily read and understand it. You may refer to the Object class file55 to learn how to document your code. Info: Your code comments can be written in Markdown format. The yiisoft/yii2-apidoc extension provides a tool for you to gen- erate pretty API documentation based on your code comments. Info: While not a requirement, we suggest your extension adhere to certain coding styles. You may refer to the core framework code style56. 3.12.3 Core Extensions Yii provides the following core extensions that are developed and maintained by the Yii developer team. They are all registered on Packagist57 and can be easily installed as described in the Using Extensions subsection. • yiisoft/yii2-apidoc58: provides an extensible and high-performance API documentation generator. It is also used to generate the core frame- work API documentation. 54 http://daringfireball.net/projects/markdown/ 55 https://github.com/yiisoft/yii2/blob/master/framework/base/Object.php 56 https://github.com/yiisoft/yii2/wiki/Core-framework-code-style 57 https://packagist.org/ 58 https://github.com/yiisoft/yii2-apidoc
  • 147. 3.12. EXTENSIONS 141 • yiisoft/yii2-authclient59: provides a set of commonly used auth clients, such as Facebook OAuth2 client, GitHub OAuth2 client. • yiisoft/yii2-bootstrap60: provides a set of widgets that encapsulate the Bootstrap61 components and plugins. • yiisoft/yii2-codeception62: provides testing support based on Codecep- tion63. • yiisoft/yii2-debug64: provides debugging support for Yii applications. When this extension is used, a debugger toolbar will appear at the bottom of every page. The extension also provides a set of standalone pages to display more detailed debug information. • yiisoft/yii2-elasticsearch65: provides the support for using Elasticsearch66. It includes basic querying/search support and also implements the Act- ive Record pattern that allows you to store active records in Elastic- search. • yiisoft/yii2-faker67: provides the support for using Faker68 to generate fake data for you. • yiisoft/yii2-gii69: provides a Web-based code generator that is highly extensible and can be used to quickly generate models, forms, modules, CRUD, etc. • yiisoft/yii2-imagine70: provides commonly used image manipulation functions based on Imagine71. • yiisoft/yii2-jui72: provides a set of widgets that encapsulate the JQuery UI73 interactions and widgets. 59 https://github.com/yiisoft/yii2-authclient 60 https://github.com/yiisoft/yii2-bootstrap 61 http://getbootstrap.com/ 62 https://github.com/yiisoft/yii2-codeception 63 http://codeception.com/ 64 https://github.com/yiisoft/yii2-debug 65 https://github.com/yiisoft/yii2-elasticsearch 66 http://www.elasticsearch.org/ 67 https://github.com/yiisoft/yii2-faker 68 https://github.com/fzaninotto/Faker 69 https://github.com/yiisoft/yii2-gii 70 https://github.com/yiisoft/yii2-imagine 71 http://imagine.readthedocs.org/ 72 https://github.com/yiisoft/yii2-jui 73 http://jqueryui.com/
  • 148. 142 CHAPTER 3. APPLICATION STRUCTURE • yiisoft/yii2-mongodb74: provides the support for using MongoDB75. It includes features such as basic query, Active Record, migrations, caching, code generation, etc. • yiisoft/yii2-redis76: provides the support for using redis77. It includes features such as basic query, Active Record, caching, etc. • yiisoft/yii2-smarty78: provides a template engine based on Smarty79. • yiisoft/yii2-sphinx80: provides the support for using Sphinx81. It in- cludes features such as basic query, Active Record, code generation, etc. • yiisoft/yii2-swiftmailer82: provides email sending features based on swiftmailer83. • yiisoft/yii2-twig84: provides a template engine based on Twig85. 74 https://github.com/yiisoft/yii2-mongodb 75 http://www.mongodb.org/ 76 https://github.com/yiisoft/yii2-redis 77 http://redis.io/ 78 https://github.com/yiisoft/yii2-smarty 79 http://www.smarty.net/ 80 https://github.com/yiisoft/yii2-sphinx 81 http://sphinxsearch.com 82 https://github.com/yiisoft/yii2-swiftmailer 83 http://swiftmailer.org/ 84 https://github.com/yiisoft/yii2-twig 85 http://twig.sensiolabs.org/
  • 149. Chapter 4 Handling Requests 4.1 Overview Each time when a Yii application handles a request, it undergoes a similar workflow. 1. A user makes a request to the entry script web/index.php. 2. The entry script loads the application configuration and creates an application instance to handle the request. 3. The application resolves the requested route with the help of the re- quest application component. 4. The application creates a controller instance to handle the request. 5. The controller creates an action instance and performs the filters for the action. 6. If any filter fails, the action is cancelled. 7. If all filters pass, the action is executed. 8. The action loads a data model, possibly from a database. 9. The action renders a view, providing it with the data model. 10. The rendered result is returned to the response application component. 11. The response component sends the rendered result to the user’s browser. The following diagram shows how an application handles a request. 143
  • 150. 144 CHAPTER 4. HANDLING REQUESTS In this section, we will describe in detail how some of these steps work. 4.2 Bootstrapping Bootstrapping refers to the process of preparing the environment before an application starts to resolve and process an incoming request. Bootstrapping is done in two places: the entry script and the application. In the entry script, class autoloaders for different libraries are registered. This includes the Composer autoloader through its autoload.php file and the Yii autoloader through its Yii class file. The entry script then loads the application configuration and creates an application instance. In the constructor of the application, the following bootstrapping work are done: 1. yiibaseApplication::preInit() is called, which configures some high priority application properties, such as yiibaseApplication:: basePath. 2. Register the yiibaseApplication::errorHandler. 3. Initialize application properties using the given application configura- tion. 4. yiibaseApplication::init() is called which in turn calls yiibase Application::bootstrap() to run bootstrapping components.
  • 151. 4.3. ROUTING 145 • Include the extension manifest file vendor/yiisoft/extensions.php. • Create and run bootstrap components declared by extensions. • Create and run application components and/or modules that are declared in the application’s bootstrap property. Because the bootstrapping work has to be done before handling every re- quest, it is very important to keep this process light and optimize it as much as possible. Try not to register too many bootstrapping components. A bootstrap- ping component is needed only if it wants to participate the whole life cycle of requesting handling. For example, if a module needs to register additional URL parsing rules, it should be listed in the bootstrap property so that the new URL rules can take effect before they are used to resolve requests. In production mode, enable bytecode cache, such as APC, to minimize the time needed for including and parsing PHP files. Some large applications have very complex application configurations which are divided into many smaller configuration files. If this is the case, consider caching the whole configuration array and loading it directly from cache before creating the application instance in the entry script. 4.3 Routing When the yiiwebApplication::run() method is called by the entry script, the first thing it does is to resolve the incoming request and instantiate an appropriate controller action to handle the request. This process is called routing. 4.3.1 Resolving Route The first step of routing is to parse the incoming request into a route which, as described in the Controllers section, is used to address a controller ac- tion. This is done by yiiwebRequest::resolve() method of the request application component. The method invokes the URL manager to do the actual request parsing work. By default, if the incoming request contains a GET parameter named r, its value will be considered as the route. However, if the yiiwebUrlManager ::enablePrettyUrl is enabled, more work will be done to determine the requested route. For more details, please refer to the URL Parsing and Generation section. In case a route cannot be determined, the request component will throw a yiiwebNotFoundHttpException.
  • 152. 146 CHAPTER 4. HANDLING REQUESTS Default Route If an incoming request does not specify a route, which often happens to the request for homepages, the route specified by yiiwebApplication:: defaultRoute will be used. The default value of this property is site/index, which refers to the index action of the site controller. You may customize this property in the application configuration like the following: return [ // ... ’defaultRoute’ => ’main/index’, ]; catchAll Route Sometimes, you may want to put your Web application in maintenance mode temporarily and display the same informational page for all requests. There are many ways to accomplish this goal. But one of the simplest ways is to configure the yiiwebApplication::catchAll property like the following in the application configuration: return [ // ... ’catchAll’ => [’site/offline’], ]; The catchAll property should take an array whose first element specifies a route, and the rest of the elements (name-value pairs) specify the parameters to be bound to the action. When the catchAll property is set, it will replace any route resolved from the incoming requests. With the above configuration, the same site/offline action will be used to handle all incoming requests. 4.3.2 Creating Action Once the requested route is determined, the next step is to create the action object corresponding to the route. The route is broken down into multiple parts by the slashes in it. For example, site/index will be broken into site and index. Each part is an ID which may refer to a module, a controller or an action. Starting from the first part in the route, the application conducts the following steps to create modules (if any), the controller and the action: 1. Set the application as the current module. 2. Check if the yiibaseModule::controllerMap of the current module contains the current ID. If so, a controller object will be created ac- cording to the controller configuration found in the map, and do Step 5 with the rest parts of the route.
  • 153. 4.4. REQUESTS 147 3. Check if the ID refers to a module listed in the yiibaseModule:: modules property of the current module. If so, a module is created according to the configuration found in the module list, and do Step 2 with the next part in the route under the context of the newly created module. 4. Treat the ID as a controller ID and create a controller object. Do the next step with the rest part of the route. 5. The controller looks for the current ID in its yiibaseController:: actions(). If found, it creates an action according to the configuration found in the map. Otherwise, the controller will attempt to create an inline action which is defined by an action method corresponding to the current ID. Among the above steps, if any error occurs, a yiiwebNotFoundHttpException will be thrown, indicating failure of the routing. 4.4 Requests Requests made to an application are represented in terms of yiiwebRequest objects which provide information such as request parameters, HTTP head- ers, cookies, etc. For a given request, you can get access to the corresponding request object via the request application component. In this section, we will describe how you can make use of this component in your applications. 4.4.1 Request Parameters To get request parameters, you can call yiiwebRequest::get() and yii webRequest::post() methods of the request component. They return the values of $_GET and $_POST, respectively. For example, $request = Yii::$app->request; $get = $request->get(); // equivalent to: $get = $_GET; $id = $request->get(’id’); // equivalent to: $id = isset($_GET[’id’]) ? $_GET[’id’] : null; $id = $request->get(’id’, 1); // equivalent to: $id = isset($_GET[’id’]) ? $_GET[’id’] : 1; $post = $request->post(); // equivalent to: $post = $_POST; $name = $request->post(’name’); // equivalent to: $name = isset($_POST[’name’]) ? $_POST[’name’] : null;
  • 154. 148 CHAPTER 4. HANDLING REQUESTS $name = $request->post(’name’, ’’); // equivalent to: $name = isset($_POST[’name’]) ? $_POST[’name’] : ’’; Info: Instead of directly accessing $_GET and $_POST to retrieve the request parameters, it is recommended that you get them via the request component like shown above. This will make writing tests easier because you can create a mock request component with faked request data. When implementing RESTful APIs, you often need to retrieve parameters that are submitted via PUT, PATCH or other request methods. You can get these parameters by calling the yiiwebRequest::getBodyParam() meth- ods. For example, $request = Yii::$app->request; // returns all parameters $params = $request->bodyParams; // returns the parameter "id" $param = $request->getBodyParam(’id’); Info: Unlike GET parameters, parameters submitted via POST, PUT, PATCH etc. are sent in the request body. The request component will parse these parameters when you access them through the methods described above. You can customize the way how these parameters are parsed by configuring the yiiwebRequest:: parsers property. 4.4.2 Request Methods You can get the HTTP method used by the current request via the ex- pression Yii::$app->request->method. A whole set of boolean properties are also provided for you to check if the current method is of certain type. For example, $request = Yii::$app->request; if ($request->isAjax) { // the request is an AJAX request } if ($request->isGet) { // the request method is GET } if ($request->isPost) { // the request method is POST } if ($request->isPut) { // the request method is PUT } 4.4.3 Request URLs The request component provides many ways of inspecting the currently re- quested URL.
  • 155. 4.4. REQUESTS 149 Assuming the URL being requested is http://example.com/admin/index.php /product?id=100, you can get various parts of this URL as summarized in the following: • yiiwebRequest::url: returns /admin/index.php/product?id=100, which is the URL without the host info part. • yiiwebRequest::absoluteUrl: returns http://example.com/admin/index .php/product?id=100, which is the whole URL including the host info part. • yiiwebRequest::hostInfo: returns http://example.com, which is the host info part of the URL. • yiiwebRequest::pathInfo: returns /product, which is the part after the entry script and before the question mark (query string). • yiiwebRequest::queryString: returns id=100, which is the part after the question mark. • yiiwebRequest::baseUrl: returns /admin, which is the part after the host info and before the entry script name. • yiiwebRequest::scriptUrl: returns /admin/index.php, which is the URL without path info and query string. • yiiwebRequest::serverName: returns example.com, which is the host name in the URL. • yiiwebRequest::serverPort: returns 80, which is the port used by the Web server. 4.4.4 HTTP Headers You can get the HTTP header information through the yiiwebHeaderCollection returned by the yiiwebRequest::headers property. For example, // $headers is an object of yiiwebHeaderCollection $headers = Yii::$app->request->headers; // returns the Accept header value $accept = $headers->get(’Accept’); if ($headers->has(’User-Agent’)) { // there is User-Agent header } The request component also provides support for quickly accessing some commonly used headers, including • yiiwebRequest::userAgent: returns the value of the User-Agent header.
  • 156. 150 CHAPTER 4. HANDLING REQUESTS • yiiwebRequest::contentType: returns the value of the Content-Type header which indicates the MIME type of the data in the request body. • yiiwebRequest::acceptableContentTypes: returns the content MIME types acceptable by users. The returned types ordered by the quality score. Types with the highest scores will be returned first. • yiiwebRequest::acceptableLanguages: returns the languages ac- ceptable by users. The returned languages are ordered by their prefer- ence level. The first element represents the most preferred language. If your application supports multiple languages and you want to display pages in the language that is the most preferred by the end user, you may use the language negotiation method yiiwebRequest::getPreferredLanguage(). This method takes a list of languages supported by your application, com- pares them with yiiwebRequest::acceptableLanguages, and returns the most appropriate language. Tip: You may also use the yiifiltersContentNegotiator filter to dynamically determine what content type and language should be used in the response. The filter implements the content negotiation on top the properties and methods described above. 4.4.5 Client Information You can get the host name and IP address of the client machine through yiiwebRequest::userHost and yiiwebRequest::userIP, respectively. For example, $userHost = Yii::$app->request->userHost; $userIP = Yii::$app->request->userIP; 4.5 Responses When an application finishes handling a request, it generates a yiiweb Response object and sends it to the end user. The response object contains information such as the HTTP status code, HTTP headers and body. The ultimate goal of Web application development is essentially to build such response objects upon various requests. In most cases you should mainly deal with the response application com- ponent. However, Yii also allows you to create your own response objects and send them to end users. In this section, we will describe how to compose and send responses to end users.
  • 157. 4.5. RESPONSES 151 4.5.1 Status Code One of the first things you would do when building a response is to state whether the request is successfully handled. This is done by setting the yiiwebResponse::statusCode property which can take one of the valid HTTP status codes1. For example, to indicate the request is successfully handled, you may set the status code to be 200, like the following: Yii::$app->response->statusCode = 200; However, in most cases you do not need to explicitly set the status code. This is because the default value of yiiwebResponse::statusCode is 200. And if you want to indicate the request is unsuccessful, you may throw an appropriate HTTP exception like the following: throw new yiiwebNotFoundHttpException; When the error handler catches an exception, it will extract the status code from the exception and assign it to the response. For the yiiweb NotFoundHttpException above, it is associated with the HTTP status 404. The following HTTP exceptions are predefined in Yii: • yiiwebBadRequestHttpException: status code 400. • yiiwebConflictHttpException: status code 409. • yiiwebForbiddenHttpException: status code 403. • yiiwebGoneHttpException: status code 410. • yiiwebMethodNotAllowedHttpException: status code 405. • yiiwebNotAcceptableHttpException: status code 406. • yiiwebNotFoundHttpException: status code 404. • yiiwebServerErrorHttpException: status code 500. • yiiwebTooManyRequestsHttpException: status code 429. • yiiwebUnauthorizedHttpException: status code 401. • yiiwebUnsupportedMediaTypeHttpException: status code 415. If the exception that you want to throw is not among the above list, you may create one by extending from yiiwebHttpException, or directly throw it with a status code, for example, throw new yiiwebHttpException(402); 1 http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
  • 158. 152 CHAPTER 4. HANDLING REQUESTS 4.5.2 HTTP Headers You can send HTTP headers by manipulating the yiiwebResponse:: headers in the response component. For example, $headers = Yii::$app->response->headers; // add a Pragma header. Existing Pragma headers will NOT be overwritten. $headers->add(’Pragma’, ’no-cache’); // set a Pragma header. Any existing Pragma headers will be discarded. $headers->add(’Pragma’, ’no-cache’); // remove Pragma header(s) and return the removed Pragma header values in array $values = $headers->remove(’Pragma’); Info: Header names are case insensitive. And the newly re- gistered headers are not sent to the user until the yiiwebResponse ::send() method is called. 4.5.3 Response Body Most responses should have a body which gives the content that you want to show to end users. If you already have a formatted body string, you may assign it to the yiiwebResponse::content property of the response. For example, Yii::$app->request->content = ’hello world!’; If you data needs to be formatted before sending to end users, you should set both of the yiiwebResponse::format and yiiwebResponse::data properties. The yiiwebResponse::format property specifies in which format should the yiiwebResponse::data be formatted as. For example, $response = Yii::$app->request; $response->format = yiiwebResponse::FORMAT_JSON; $response->data = [’message’ => ’hello world’]; Yii supports the following formats out of box, each implemented by a yii webResponseFormatterInterface class. You can customize these format- ters or add new ones by configuring the yiiwebResponse::formatters property. • yiiwebResponse::FORMAT_HTML: implemented by yiiwebHtmlResponseFormatter. • yiiwebResponse::FORMAT_XML: implemented by yiiwebXmlResponseFormatter. • yiiwebResponse::FORMAT_JSON: implemented by yiiwebJsonResponseFormatter. • yiiwebResponse::FORMAT_JSONP: implemented by yiiwebJsonResponseFormatter.
  • 159. 4.5. RESPONSES 153 While response body can be set explicitly as shown above, in most cases you may set it implicitly by the return value of action methods. A common use case is like the following: public function actionIndex() { return $this->render(’index’); } The index action above returns the rendering result of the index view. The return value will be taken by the response component, formatted and then sent to end users. Because by default, the response format is as yiiwebResponse::FORMAT_HTML, you should only return a string in an action method. If you want to use a different response format, you should set it first before returning the data. For example, public function actionInfo() { Yii::$app->response->format = yiiwebResponse::FORMAT_JSON; return [ ’message’ => ’hello world’, ’code’ => 100, ]; } As aforementioned, besides using the default response application component, you can also create your own response objects and send them to end users. You can do so by returning such an object in an action method, like the following, public function actionInfo() { return Yii::createObject([ ’class’ => ’yiiwebResponse’, ’format’ => yiiwebResponse::FORMAT_JSON, ’data’ => [ ’message’ => ’hello world’, ’code’ => 100, ], ]); } Note: If you are creating your own response objects, you will not be able to take advantage of the configurations that you set for the response component in the application configuration. You can, however, use dependency injection to apply common configuration to your new response objects.
  • 160. 154 CHAPTER 4. HANDLING REQUESTS 4.5.4 Browser Redirection Browser redirection relies on sending a Location HTTP header. Because this feature is commonly used, Yii provides some special supports for it. You can redirect the user browser to a URL by calling the yiiweb Response::redirect() method. The method sets the appropriate Location header with the given URL and returns the response object itself. In an action method, you can call its shortcut version yiiwebController ::redirect(). For example, public function actionOld() { return $this->redirect(’http://example.com/new’, 301); } In the above code, the action method returns the result of the redirect() method. As explained before, the response object returned by an action method will be used as the response sending to end users. In places other than an action method, you should call yiiwebResponse ::redirect() directly followed by a call to the yiiwebResponse::send() method to ensure no extra content will be appended to the response. Yii::$app->response->redirect(’http://example.com/new’, 301)->send(); Info: By default, the yiiwebResponse::redirect() method sets the response status code to be 302 which instructs the browser that the resource being requested is temporarily located in a dif- ferent URI. You can pass in a status code 301 to tell the browser that the resource has been permanently relocated. When the current request is an AJAX request, sending a Location header will not automatically cause the browser redirection. To solve this problem, the yiiwebResponse::redirect() method sets an X-Redirect header with the redirection URL as its value. On the client side you may write JavaScript code to read this header value and redirect the browser accordingly. Info: Yii comes with a yii.js JavaScript file which provides a set of commonly used JavaScript utilities, including browser redirec- tion based on the X-Redirect header. Therefore, if you are using this JavaScript file (by registering the yiiwebYiiAsset asset bundle), you do not need to write anything to support AJAX redirection. 4.5.5 Sending Files Like browser redirection, file sending is another feature that relies on specific HTTP headers. Yii provides a set of methods to support various file sending needs. They all have built-in support for HTTP range header.
  • 161. 4.5. RESPONSES 155 • yiiwebResponse::sendFile(): sends an existing file to client. • yiiwebResponse::sendContentAsFile(): sends a text string as a file to client. • yiiwebResponse::sendStreamAsFile(): sends an existing file stream as a file to client. These methods have the same method signature with the response object as the return value. If the file to be sent is very big, you should consider using yiiwebResponse::sendStreamAsFile() because it is more memory efficient. The following example shows how to send a file in a controller action: public function actionDownload() { return Yii::$app->response->sendFile(’path/to/file.txt’); } If you are calling the file sending method in places other than an action method, you should also call the yiiwebResponse::send() method after- wards to ensure no extra content will be appended to the response. Yii::$app->response->sendFile(’path/to/file.txt’)->send(); Some Web servers have a special file sending support called X-Sendfile. The idea is to redirect the request for a file to the Web server which will directly serve the file. As a result, the Web application can terminate earlier while the Web server is sending the file. To use this feature, you may call the yiiwebResponse::xSendFile(). The following list summarizes how to enable the X-Sendfile feature for some popular Web servers: • Apache: X-Sendfile2 • Lighttpd v1.4: X-LIGHTTPD-send-file3 • Lighttpd v1.5: X-Sendfile4 • Nginx: X-Accel-Redirect5 • Cherokee: X-Sendfile and X-Accel-Redirect6 2 http://tn123.org/mod_xsendfile 3 http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file 4 http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file 5 http://wiki.nginx.org/XSendfile 6 http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile
  • 162. 156 CHAPTER 4. HANDLING REQUESTS 4.5.6 Sending Response The content in a response is not sent to the user until the yiiwebResponse ::send() method is called. By default, this method will be called automat- ically at the end of yiibaseApplication::run(). You can, however, explicitly call this method to force sending out the response immediately. The yiiwebResponse::send() method takes the following steps to send out a response: 1. Trigger the yiiwebResponse::EVENT_BEFORE_SEND event. 2. Call yiiwebResponse::prepare() to format yiiwebResponse:: data into yiiwebResponse::content. 3. Trigger the yiiwebResponse::EVENT_AFTER_PREPARE event. 4. Call yiiwebResponse::sendHeaders() to send out the registered HTTP headers. 5. Call yiiwebResponse::sendContent() to send out the response body content. 6. Trigger the yiiwebResponse::EVENT_AFTER_SEND event. After the yiiwebResponse::send() method is called once, any further call to this method will be ignored. This means once the response is sent out, you will not be able to append more content to it. As you can see, the yiiwebResponse::send() method triggers several useful events. By responding to these events, it is possible to adjust or decorate the response.
  • 163. 4.5. RESPONSES 157 Error: not existing file: runtime-sessions-cookies.md
  • 164. 158 CHAPTER 4. HANDLING REQUESTS 4.6 URL Management Note: This section is under development. The concept of URL management in Yii is fairly simple. URL management is based on the premise that the application uses internal routes and parameters everywhere. The framework itself will then translate routes into URLs, and vice versa, according to the URL manager’s configuration. This approach allows you to change site-wide URLs merely by editing a single configuration file, without ever touching the application code. 4.6.1 Internal routes When implementing an application using Yii, you’ll deal with internal routes, often referred to as routes and parameters. Each controller and controller action has a corresponding internal route such as site/index or user/create. In the first example, site is referred to as the controller ID while index is referred to as the action ID. In the second example, user is the controller ID and create is the action ID. If the controller belongs to a module, the internal route is prefixed with the module ID. For example blog/post/index for a blog module (with post being the controller ID and index being the action ID). 4.6.2 Creating URLs The most important rule for creating URLs in your site is to always do so using the URL manager. The URL manager is a built-in application component named urlManager. This component is accessible from both web and console applications via Yii::$app->urlManager. The component makes available the two following URL creation methods: • createUrl($params) • createAbsoluteUrl($params, $schema = null) The createUrl method creates an URL relative to the application root, such as /index.php/site/index/. The createAbsoluteUrl method creates an URL prefixed with the proper protocol and hostname: http://www.example.com/ index.php/site/index. The former is suitable for internal application URLs, while the latter is used when you need to create URLs for external resources, such as connecting to third party services, sending email, generating RSS feeds etc. Some examples: echo Yii::$app->urlManager->createUrl([’site/page’, ’id’ => ’about’]); // /index.php/site/page/id/about/ echo Yii::$app->urlManager->createUrl([’date-time/fast-forward’, ’id’ => 105])
  • 165. 4.6. URL MANAGEMENT 159 // /index.php?r=date-time/fast-forward&id=105 echo Yii::$app->urlManager->createAbsoluteUrl(’blog/post/index’); // http://www.example.com/index.php/blog/post/index/ The exact format of the URL depends on how the URL manager is con- figured. The above examples may also output: • /site/page/id/about/ • /index.php?r=site/page&id=about • /index.php?r=date-time/fast-forward&id=105 • /index.php/date-time/fast-forward?id=105 • http://www.example.com/blog/post/index/ • http://www.example.com/index.php?r=blog/post/index In order to simplify URL creation there is yiihelpersUrl helper. Assum- ing we’re at /index.php?r=management/default/users&id=10 the following is how Url helper works: use yiihelpersUrl; // currently active route // /index.php?r=management/default/users echo Url::to(’’); // same controller, different action // /index.php?r=management/default/page&id=contact echo Url::toRoute([’page’, ’id’ => ’contact’]); // same module, different controller and action // /index.php?r=management/post/index echo Url::toRoute(’post/index’); // absolute route no matter what controller is making this call // /index.php?r=site/index echo Url::toRoute(’/site/index’); // url for the case sensitive action ‘actionHiTech‘ of the current controller // /index.php?r=management/default/hi-tech echo Url::toRoute(’hi-tech’); // url for action the case sensitive controller, ‘DateTimeController:: actionFastForward‘ // /index.php?r=date-time/fast-forward&id=105 echo Url::toRoute([’/date-time/fast-forward’, ’id’ => 105]); // get URL from alias
  • 166. 160 CHAPTER 4. HANDLING REQUESTS // http://google.com/ Yii::setAlias(’@google’, ’http://google.com/’); echo Url::to(’@google’); // get home URL // /index.php?r=site/index echo Url::home(); Url::remember(); // save URL to be used later Url::previous(); // get previously saved URL Tip: In order to generate URL with a hashtag, for example /index .php?r=site/page&id=100#title, you need to specify the parameter named # using Url::to([’post/read’, ’id’ => 100, ’#’ => ’title’ ]). There’s also Url::canonical() method that allows you to generate canonical URL7 for the currently executing action. The method ignores all action parameters except ones passed via action arguments: namespace appcontrollers; use yiiwebController; use yiihelpersUrl; class CanonicalController extends Controller { public function actionTest($page) { echo Url::canonical(); } } When accessed as /index.php?r=canonical/test&page=hello&number=42 canonical URL will be /index.php?r=canonical/test&page=hello. 4.6.3 Customizing URLs By default, Yii uses a query string format for URLs, such as /index.php?r =news/view&id=100. In order to make URLs human-friendly i.e., more read- able, you need to configure the urlManager component in the application’s configuration file. Enabling “pretty” URLs will convert the query string format to a directory-based format: /index.php/news/view?id=100. Disabling the showScriptName parameter means that index.php will not be part of the URLs. Here’s the relevant part of the application’s configuration file: <?php return [ // ... 7 https://en.wikipedia.org/wiki/Canonical_link_element
  • 167. 4.6. URL MANAGEMENT 161 ’components’ => [ ’urlManager’ => [ ’enablePrettyUrl’ => true, ’showScriptName’ => false, ], ], ]; Note that this configuration will only work if the web server has been properly configured for Yii, see installation. Named parameters A rule can be associated with a few GET parameters. These GET parameters appear in the rule’s pattern as special tokens in the following format: <ParameterName:ParameterPattern> ParameterName is a name of a GET parameter, and the optional ParameterPattern is the regular expression that should be used to match the value of the GET parameter. In case ParameterPattern is omitted, it means the parameter should match any characters except /. When creating a URL, these para- meter tokens will be replaced with the corresponding parameter values; when parsing a URL, the corresponding GET parameters will be populated with the parsed results. Let’s use some examples to explain how URL rules work. We assume that our rule set consists of three rules: [ ’posts’=>’post/list’, ’post/<id:d+>’=>’post/read’, ’post/<year:d{4}>/<title>’=>’post/read’, ] • Calling Url::toRoute(’post/list’) generates /index.php/posts. The first rule is applied. • Calling Url::toRoute([’post/read’, ’id’ => 100]) generates /index.php/ post/100. The second rule is applied. • Calling Url::toRoute([’post/read’, ’year’ => 2008, ’title’ => ’a sample post’]) generates /index.php/post/2008/a%20sample%20post. The third rule is applied. • Calling Url::toRoute(’post/read’) generates /index.php/post/read. None of the rules is applied, convention is used instead. In summary, when using createUrl to generate a URL, the route and the GET parameters passed to the method are used to decide which URL rule to be applied. If every parameter associated with a rule can be found in the GET
  • 168. 162 CHAPTER 4. HANDLING REQUESTS parameters passed to createUrl, and if the route of the rule also matches the route parameter, the rule will be used to generate the URL. If the GET parameters passed to Url::toRoute are more than those required by a rule, the additional parameters will appear in the query string. For example, if we call Url::toRoute([’post/read’, ’id’ => 100, ’year’ => 2008]), we will obtain /index.php/post/100?year=2008. As we mentioned earlier, the other purpose of URL rules is to parse the requesting URLs. Naturally, this is an inverse process of URL creation. For example, when a user requests for /index.php/post/100, the second rule in the above example will apply, which resolves in the route post/read and the GET parameter [’id’ => 100] (accessible via Yii::$app->request->get(’id’)). Parameterizing Routes We may reference named parameters in the route part of a rule. This allows a rule to be applied to multiple routes based on matching criteria. It may also help reduce the number of rules needed for an application, and thus improve the overall performance. We use the following example rules to illustrate how to parameterize routes with named parameters: [ ’<controller:(post|comment)>/<id:d+>/<action:(create|update|delete)>’ => ’<controller>/<action>’, ’<controller:(post|comment)>/<id:d+>’ => ’<controller>/read’, ’<controller:(post|comment)>s’ => ’<controller>/list’, ] In the above example, we use two named parameters in the route part of the rules: controller and action. The former matches a controller ID to be either post or comment, while the latter matches an action ID to be create, update or delete. You may name the parameters differently as long as they do not conflict with GET parameters that may appear in URLs. Using the above rules, the URL /index.php/post/123/create will be parsed as the route post/create with GET parameter id=123. Given the route comment /list and GET parameter page=2, we can create a URL /index.php/comments? page=2. Parameterizing hostnames It is also possible to include hostnames in the rules for parsing and creating URLs. One may extract part of the hostname to be a GET parameter. This is especially useful for handling subdomains. For example, the URL http:// admin.example.com/en/profile may be parsed into GET parameters user=admin and lang=en. On the other hand, rules with hostname may also be used to create URLs with parameterized hostnames.
  • 169. 4.6. URL MANAGEMENT 163 In order to use parameterized hostnames, simply declare URL rules with host info, e.g.: [ ’http://<user:w+>.example.com/<lang:w+>/profile’ => ’user/profile’, ] In the above example the first segment of the hostname is treated as the user parameter while the first segment of the path info is treated as the lang parameter. The rule corresponds to the user/profile route. Note that yiiwebUrlManager::showScriptName will not take effect when a URL is being created using a rule with a parameterized hostname. Also note that any rule with a parameterized hostname should NOT contain the subfolder if the application is under a subfolder of the Web root. For example, if the application is under http://www.example.com/sandbox/blog, then we should still use the same URL rule as described above without the subfolder sandbox/blog. Faking URL Suffix <?php return [ // ... ’components’ => [ ’urlManager’ => [ ’suffix’ => ’.html’, ], ], ]; Handling REST requests TBD: - RESTful routing: yiifiltersVerbFilter, yiiwebUrlManager ::$rules - Json API: - response: yiiwebResponse::format - request: yiiwebRequest::$parsers, yiiwebJsonParser 4.6.4 URL parsing Complimentary to creating URLs Yii also handles transforming custom URLs back into internal routes and parameters. Strict URL parsing By default if there’s no custom rule for a URL and the URL matches the default format such as /site/page, Yii tries to run the corresponding con- troller’s action. This behavior can be disabled so if there’s no custom rule match, a 404 not found error will be produced immediately.
  • 170. 164 CHAPTER 4. HANDLING REQUESTS <?php return [ // ... ’components’ => [ ’urlManager’ => [ ’enableStrictParsing’ => true, ], ], ]; 4.6.5 Creating your own rule classes yiiwebUrlRule class is used for both parsing URL into parameters and creating URL based on parameters. Despite the fact that default implement- ation is flexible enough for the majority of projects, there are situations when using your own rule class is the best choice. For example, in a car dealer web- site, we may want to support the URL format like /Manufacturer/Model, where Manufacturer and Model must both match some data in a database table. The default rule class will not work because it mostly relies on statically declared regular expressions which have no database knowledge. We can write a new URL rule class by extending from yiiwebUrlRule and use it in one or multiple URL rules. Using the above car dealer website as an example, we may declare the following URL rules in application config: // ... ’components’ => [ ’urlManager’ => [ ’rules’ => [ ’<action:(login|logout|about)>’ => ’site/<action>’, // ... [’class’ => ’appcomponentsCarUrlRule’, ’connectionID’ => ’db’, /* ... */], ], ], ], In the above, we use the custom URL rule class CarUrlRule to handle the URL format /Manufacturer/Model. The class can be written like the following: namespace appcomponents; use yiiwebUrlRule; class CarUrlRule extends UrlRule { public $connectionID = ’db’; public function init() {
  • 171. 4.7. ERROR HANDLING 165 if ($this->name === null) { $this->name = __CLASS__; } } public function createUrl($manager, $route, $params) { if ($route === ’car/index’) { if (isset($params[’manufacturer’], $params[’model’])) { return $params[’manufacturer’] . ’/’ . $params[’model’]; } elseif (isset($params[’manufacturer’])) { return $params[’manufacturer’]; } } return false; // this rule does not apply } public function parseRequest($manager, $request) { $pathInfo = $request->getPathInfo(); if (preg_match(’%^(w+)(/(w+))?$%’, $pathInfo, $matches)) { // check $matches[1] and $matches[3] to see // if they match a manufacturer and a model in the database // If so, set $params[’manufacturer’] and/or $params[’model’] // and return [’car/index’, $params] } return false; // this rule does not apply } } Besides the above usage, custom URL rule classes can also be implemented for many other purposes. For example, we can write a rule class to log the URL parsing and creation requests. This may be useful during development stage. We can also write a rule class to display a special 404 error page in case all other URL rules fail to resolve the current request. Note that in this case, the rule of this special class must be declared as the last rule. 4.7 Error Handling Note: This section is under development. Error handling in Yii is different than handling errors in plain PHP. First of all, Yii will convert all non-fatal errors to exceptions: use yiibaseErrorException; use Yii; try { 10/0; } catch (ErrorException $e) { Yii::warning("Tried dividing by zero."); }
  • 172. 166 CHAPTER 4. HANDLING REQUESTS // execution may continue As demonstrated above you may handle errors using try-catch. Second, even fatal errors in Yii are rendered in a nice way. This means that in debugging mode, you can trace the causes of fatal errors in order to more quickly identify the cause of the problem. 4.7.1 Rendering errors in a dedicated controller action The default Yii error page is great when developing a site, and is acceptable for production sites if YII_DEBUG is turned off in your bootstrap index.php file. But you may want to customize the default error page to make it more suitable for your project. The easiest way to create a custom error page it is to use a dedicated controller action for error rendering. First, you’ll need to configure the errorHandler component in the application’s configuration: // ... ’components’ => [ // ... ’errorHandler’ => [ ’errorAction’ => ’site/error’, ], ] With that configuration in place, whenever an error occurs, Yii will execute the error-action of the site-controller. That action should look for an excep- tion and, if present, render the proper view file, passing along the exception: public function actionError() { $exception = Yii::$app->errorHandler->exception; if ($exception !== null) { return $this->render(’error’, [’exception’ => $exception]); } } Next, you would create the views/site/error.php file, which would make use of the exception. The exception object has the following properties: • statusCode: the HTTP status code (e.g. 403, 500). Available for yii webHttpException only. • code: the code of the exception. • message: the error message. • file: the name of the PHP script file where the error occurs. • line: the line number of the code where the error occurs. • trace: the call stack of the error.
  • 173. 4.8. LOGGING 167 4.7.2 Rendering errors without a dedicated controller action Instead of creating a dedicated action within the Site controller, you could just indicate to Yii what class should be used to handle errors: public function actions() { return [ ’error’ => [ ’class’ => ’yiiwebErrorAction’, ], ]; } After associating the class with the error as in the above, define the views/ site/error.php file, which will automatically be used. The view will be passed three variables: • $name: the error name • $message: the error message • $exception: the exception being handled The $exception object will have the same properties as outlined above. 4.8 Logging Note: This section is under development. Yii provides flexible and extensible logger that is able to handle messages according to severity level or their type. You may filter messages by multiple criteria and forward them to files, email, debugger etc. 4.8.1 Logging basics Basic logging is as simple as calling one method: Yii::info(’Hello, I am a test log message’); You can log simple strings as well as more complex data structures such as arrays or objects. When logging data that is not a string the defaulf yii log targets will serialize the value using yiihelpersVardumper::export(). Message category Additionally to the message itself message category could be specified in order to allow filtering such messages and handing these differently. Message category is passed as a second argument of logging methods and is application by default.
  • 174. 168 CHAPTER 4. HANDLING REQUESTS Severity levels There are multiple severity levels and corresponding methods available: • Yii::trace used maily for development purpose to indicate workflow of some code. Note that it only works in development mode when YII_DEBUG is set to true. • Yii::error used when there’s unrecoverable error. • Yii::warning used when an error occurred but execution can be con- tinued. • Yii::info used to keep record of important events such as adminis- trator logins. 4.8.2 Log targets When one of the logging methods is called, message is passed to yiilog Logger component accessible as Yii::getLogger(). Logger accumulates mes- sages in memory and then when there are enough messages or when the current request finishes, sends them to different log targets, such as file or email. You may configure the targets in application configuration, like the fol- lowing: [ ’bootstrap’ => [’log’], // ensure logger gets loaded before application starts ’components’ => [ ’log’ => [ ’targets’ => [ ’file’ => [ ’class’ => ’yiilogFileTarget’, ’levels’ => [’trace’, ’info’], ’categories’ => [’yii*’], ], ’email’ => [ ’class’ => ’yiilogEmailTarget’, ’levels’ => [’error’, ’warning’], ’message’ => [ ’to’ => [’[email protected]’, ’[email protected] ’], ’subject’ => ’New example.com log message’, ], ], ], ], ], ]
  • 175. 4.8. LOGGING 169 In the config above we are defining two log targets: yiilogFileTarget and yiilogEmailTarget. In both cases we are filtering messages handles by these targets by severity. In case of file target we’re additionally filter by category. yii* means all categories starting with yii. Each log target can have a name and can be referenced via the yiilog Logger::targets property as follows: Yii::$app->log->targets[’file’]->enabled = false; When the application ends or yiilogLogger::flushInterval is reached, Logger will call yiilogLogger::flush() to send logged messages to dif- ferent log targets, such as file, email, web. Note: In the above configuration we added the log component to the list of bootstrap components that get initialized when the application is initialized to ensure logging is enabled from the start. 4.8.3 Profiling Performance profiling is a special type of message logging that can be used to measure the time needed for the specified code blocks to execute and find out what the performance bottleneck is. To use it we need to identify which code blocks need to be profiled. Then we mark the beginning and the end of each code block by inserting the following methods: Yii::beginProfile(’myBenchmark’); ...code block being profiled... Yii::endProfile(’myBenchmark’); where myBenchmark uniquely identifies the code block. Note, code blocks need to be nested properly such as Yii::beginProfile(’block1’); // some code to be profiled Yii::beginProfile(’block2’); // some other code to be profiled Yii::endProfile(’block2’); Yii::endProfile(’block1’); Profiling results could be displayed in debugger.
  • 176. 170 CHAPTER 4. HANDLING REQUESTS
  • 177. Chapter 5 Key Concepts 5.1 Components Components are the main building blocks of Yii applications. Components are instances of yiibaseComponent, or an extended class. The three main features that components provide to other classes are: • Properties • Events • Behaviors, Separately and combined, these features make Yii classes much more custom- izable and easier to use. For example, the included yiijuiDatePicker, a user interface component, can be used in a view to generate an interactive date picker: use yiijuiDatePicker; echo DatePicker::widget([ ’language’ => ’ru’, ’name’ => ’country’, ’clientOptions’ => [ ’dateFormat’ => ’yy-mm-dd’, ], ]); The widget’s properties are easily writable because the class extends yii baseComponent. While components are very powerful, they are a bit heavier than normal objects, due to the fact that it takes extra memory and CPU time to support event and behavior functionality in particular. If your components do not need these two features, you may consider extending your component class from yiibaseObject instead of yiibaseComponent. Doing so will make 171
  • 178. 172 CHAPTER 5. KEY CONCEPTS your components as efficient as normal PHP objects, but with added support for properties. When extending your class from yiibaseComponent or yiibaseObject, it is recommended that you follow these conventions: • If you override the constructor, specify a $config parameter as the con- structor’s last parameter, and then pass this parameter to the parent constructor. • Always call the parent constructor at the end of your overriding con- structor. • If you override the yiibaseObject::init() method, make sure you call the parent implementation of init at the beginning of your init method. For example: namespace yiicomponentsMyClass; use yiibaseObject; class MyClass extends Object { public $prop1; public $prop2; public function __construct($param1, $param2, $config = []) { // ... initialization before configuration is applied parent::__construct($config); } public function init() { parent::init(); // ... initialization after configuration is applied } } Following these guidelines will make your components configurable when they are created. For example: $component = new MyClass(1, 2, [’prop1’ => 3, ’prop2’ => 4]); // alternatively $component = Yii::createObject([ ’class’ => MyClass::className(), ’prop1’ => 3, ’prop2’ => 4, ], [1, 2]);
  • 179. 5.2. PROPERTIES 173 Info: While the approach of calling Yii::createObject() looks more complicated, it is more powerful because it is implemented on top of a dependency injection container. The yiibaseObject class enforces the following object lifecycle: 1. Pre-initialization within the constructor. You can set default property values here. 2. Object configuration via $config. The configuration may overwrite the default values set within the constructor. 3. Post-initialization within yiibaseObject::init(). You may over- ride this method to perform sanity checks and normalization of the properties. 4. Object method calls. The first three steps all happen within the object’s constructor. This means that once you get a class instance (i.e., an object), that object has already been initialized to a proper, reliable state. 5.2 Properties In PHP, class member variables are also called properties. These variables are part of the class definition, and are used to represent the state of a class instance (i.e., to differentiate one instance of the class from another). In practice, you may often want to handle the reading or writing of properties in special ways. For example, you may want to always trim a string when it is being assigned to a label property. You could use the following code to achieve this task: $object->label = trim($label); The drawback of the above code is that you would have to call trim() every- where in your code where you might set the label property. If, in the future, the label property gets a new requirement, such as the first letter must be captialized, you would again have to modify every bit of code that assigns a value to label. The repetition of code leads to bugs, and is a practice you want to avoid as much as possible. To solve this problem, Yii introduces a base class called yiibaseObject that supports defining properties based on getter and setter class methods. If a class needs that functionality, it should extend from yiibaseObject, or from a child class.
  • 180. 174 CHAPTER 5. KEY CONCEPTS Info: Nearly every core class in the Yii framework extends from yiibaseObject or a child class. This means that whenever you see a getter or setter in a core class, you can use it like a property. A getter method is a method whose name starts with the word get; a setter method starts with set. The name after the get or set prefix defines the name of a property. For example, a getter getLabel() and/or a setter setLabel() defines a property named label, as shown in the following code: namespace appcomponents; use yiibaseObject; class Foo extend Object { private $_label; public function getLabel() { return $this->_label; } public function setLabel($value) { $this->_label = trim($value); } } (To be clear, the getter and setter methods create the property label, which in this case internally refer to a private attributed named _label.) Properties defined by getters and setters can be used like class member variables. The main difference is that when such a property is being read, the corresponding getter method will be called; when the property is be- ing assigned a value, the corresponding setter method will be called. For example: // equivalent to $label = $object->getLabel(); $label = $object->label; // equivalent to $object->setLabel(’abc’); $object->label = ’abc’; A property defined by a getter without a setter is read only. Trying to assign a value to such a property will cause an yiibaseInvalidCallException. Similarly, a property defined by a setter without a getter is write only, and trying to read such a property will also cause an exception. It is not common to have write-only properties. There are several special rules for, and limitations on, the properties defined via getters and setters:
  • 181. 5.3. EVENTS 175 • The names of such properties are case-insensitive. For example, $object ->label and $object->Label are the same. This is because method names in PHP are case-insensitive. • If the name of such a property is the same as a class member variable, the latter will take precedence. For example, if the above Foo class has a member variable label, then the assignment $object->label = ’abc’ will affect the member variable ‘label’; that line would not call the setLabel() setter method. • These properties do not support visibility. It makes no difference for the visibility of a property if the defining getter or setter method is public, protected or private. • The properties can only be defined by non-static getters and/or setters. Static methods will not be treated in this same manner. Returning back to the problem described at the beginning of this guide, instead of calling trim() everywhere a label value is assigned, trim() now only needs to be invoked within the setter setLabel(). And if a new requirement comes that requires the label be initially capitalized, the setLabel() method can quickly be modified without touching any other code. The one change will universally affect every assignment to label. 5.3 Events Events allow you to inject custom code into existing code at certain execution points. You can attach custom code to an event so that when the event is triggered, the code gets executed automatically. For example, a mailer object may trigger a messageSent event when it successfully sends a message. If you want to keep track of the messages that are successfully sent, you could then simply attach the tracking code to the messageSent event. Yii introduces a base class called yiibaseComponent to support events. If a class needs to trigger events, it should extend from yiibaseComponent, or from a child class. 5.3.1 Event Handlers An event handler is a PHP callback1 that gets executed when the event it is attached to is triggered. You can use any of the following callbacks: • a global PHP function specified as a string (without parentheses), e.g., ’trim’; 1 http://www.php.net/manual/en/language.types.callable.php
  • 182. 176 CHAPTER 5. KEY CONCEPTS • an object method specified as an array of an object and a method name as a string (without parenthess), e.g., [$object, ’methodName’]; • a static class method specified as an array of a class name and a method name as a string (without parentheses), e.g., [$class, ’methodName’]; • an anonymous function, e.g., function ($event) { ... }. The signature of an event handler is: function ($event) { // $event is an object of yiibaseEvent or a child class } Through the $event parameter, an event handler may get the following in- formation about the event that occurred: • yiibaseEvent::name • yiibaseEvent::sender: the object whose trigger() method was called • yiibaseEvent::data: the data that is provided when attaching the event handler (to be explained next) 5.3.2 Attaching Event Handlers You can attach a handler to an event by calling the yiibaseComponent:: on() method. For example: $foo = new Foo; // this handler is a global function $foo->on(Foo::EVENT_HELLO, ’function_name’); // this handler is an object method $foo->on(Foo::EVENT_HELLO, [$object, ’methodName’]); // this handler is a static class method $foo->on(Foo::EVENT_HELLO, [’appcomponentsBar’, ’methodName’]); // this handler is an anonymous function $foo->on(Foo::EVENT_HELLO, function ($event) { // event handling logic }); You may also attach event handlers through configurations. For more details, please refer to the Configurations section. When attaching an event handler, you may provide additional data as the third parameter to yiibaseComponent::on(). The data will be made available to the handler when the event is triggered and the handler is called. For example:
  • 183. 5.3. EVENTS 177 // The following code will display "abc" when the event is triggered // because $event->data contains the data passed as the 3rd argument to "on" $foo->on(Foo::EVENT_HELLO, ’function_name’, ’abc’); function function_name($event) { echo $event->data; } 5.3.3 Event Handler Order You may attach one or more handlers to a single event. When an event is triggered, the attached handlers will be called in the order that they were attached to the event. If a handler needs to stop the invocation of the handlers that follow it, it may set the yiibaseEvent::handled property of the $event parameter to be true: $foo->on(Foo::EVENT_HELLO, function ($event) { $event->handled = true; }); By default, a newly attached handler is appended to the existing handler queue for the event. As a result, the handler will be called in the last place when the event is triggered. To insert the new handler at the start of the handler queue so that the handler gets called first, you may call yiibase Component::on(), passing false for the fourth parameter $append: $foo->on(Foo::EVENT_HELLO, function ($event) { // ... }, $data, false); 5.3.4 Triggering Events Events are triggered by calling the yiibaseComponent::trigger() method. The method requires an event name, and optionally an event object that de- scribes the parameters to be passed to the event handlers. For example: namespace appcomponents; use yiibaseComponent; use yiibaseEvent; class Foo extends Component { const EVENT_HELLO = ’hello’; public function bar() { $this->trigger(self::EVENT_HELLO); } } With the above code, any calls to bar() will trigger an event named hello.
  • 184. 178 CHAPTER 5. KEY CONCEPTS Tip: It is recommended to use class constants to represent event names. In the above example, the constant EVENT_HELLO repres- ents the hello event. This approach has three benefits. First, it prevents typos. Second, it can make events recognizable for IDE auto-completion support. Third, you can tell what events are supported in a class by simply checking its constant declara- tions. Sometimes when triggering an event you may want to pass along additional information to the event handlers. For example, a mailer may want pass the message information to the handlers of the messageSent event so that the handlers can know the particulars of the sent messages. To do so, you can provide an event object as the second parameter to the yiibaseComponent ::trigger() method. The event object must be an instance of the yiibase Event class, or of a child class. For example: namespace appcomponents; use yiibaseComponent; use yiibaseEvent; class MessageEvent extends Event { public $message; } class Mailer extends Component { const EVENT_MESSAGE_SENT = ’messageSent’; public function send($message) { // ...sending $message... $event = new MessageEvent; $event->message = $message; $this->trigger(self::EVENT_MESSAGE_SENT, $event); } } When the yiibaseComponent::trigger() method is called, it will call all handlers attached to the named event. 5.3.5 Detaching Event Handlers To detach a handler from an event, call the yiibaseComponent::off() method. For example: // the handler is a global function $foo->off(Foo::EVENT_HELLO, ’function_name’);
  • 185. 5.3. EVENTS 179 // the handler is an object method $foo->off(Foo::EVENT_HELLO, [$object, ’methodName’]); // the handler is a static class method $foo->off(Foo::EVENT_HELLO, [’appcomponentsBar’, ’methodName’]); // the handler is an anonymous function $foo->off(Foo::EVENT_HELLO, $anonymousFunction); Note that in general you should not try to detach an anonymous function unless you store it somewhere when it is attached to the event. In the above example, it is assumed that the anonymous function is stored as a variable $anonymousFunction. To detach ALL handlers from an event, simply call yiibaseComponent ::off() without the second parameter: $foo->off(Foo::EVENT_HELLO); 5.3.6 Class-Level Event Handlers The above subsections described how to attach a handler to an event on an instance level. Sometimes, you may want to respond to an event triggered by every instance of a class instead of only by a specific instance. Instead of attaching an event handler to every instance, you may attach the handler on the class level by calling the static method yiibaseEvent::on(). For example, an Active Record object will trigger an yiibaseActiveRecord ::EVENT_AFTER_INSERT event whenever it inserts a new record into the data- base. In order to track insertions done by every Active Record object, you may use the following code: use Yii; use yiibaseEvent; use yiidbActiveRecord; Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { Yii::trace(get_class($event->sender) . ’ is inserted’); }); The event handler will be invoked whenever an instance of yiibaseActiveRecord, or one of its child classes, triggers the yiibaseActiveRecord::EVENT_AFTER_INSERT event. In the handler, you can get the object that triggered the event through $event->sender. When an object triggers an event, it will first call instance-level handlers, followed by the class-level handlers. You may trigger a class-level event by calling the static method yiibase Event::trigger(). A class-level event is not associated with a particular object. As a result, it will cause the invocation of class-level event handlers only. For example:
  • 186. 180 CHAPTER 5. KEY CONCEPTS use yiibaseEvent; Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { echo $event->sender; // displays "appmodelsFoo" }); Event::trigger(Foo::className(), Foo::EVENT_HELLO); Note that, in this case, $event->sender refers to the name of the class trigger- ing the event instead of an object instance. Note: Because a class-level handler will respond to an event triggered by any instance of that class, or any child classes, you should use it carefully, especially if the class is a low-level base class, such as yiibaseObject. To detach a class-level event handler, call yiibaseEvent::off(). For example: // detach $handler Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); // detach all handlers of Foo::EVENT_HELLO Event::off(Foo::className(), Foo::EVENT_HELLO); 5.3.7 Global Events Yii supports a so-called global event, which is actually a trick based on the event mechanism described above. The global event requires a globally ac- cessible Singleton, such as the application instance itself. To create the global evant, an event sender calls the Singleton’s trigger() method to trigger the event, instead of calling the sender’s own trigger() method. Similarly, the event handlers are attached to the event on the Singleton. For example: use Yii; use yiibaseEvent; use appcomponentsFoo; Yii::$app->on(’bar’, function ($event) { echo get_class($event->sender); // displays "appcomponentsFoo" }); Yii::$app->trigger(’bar’, new Event([’sender’ => new Foo])); A benefit of using global events is that you do not need an object when at- taching a handler to the event which will be triggered by the object. Instead, the handler attachment and the event triggering are both done through the Singleton (e.g. the application instance).
  • 187. 5.4. BEHAVIORS 181 However, because the namespace of the global events is shared by all parties, you should name the global events wisely, such as introducing some sort of namespace (e.g. “frontend.mail.sent”, “backend.mail.sent”). 5.4 Behaviors Behaviors are instances of yiibaseBehavior, or of a child class. Beha- viors, also known as mixins2, allow you to enhance the functionality of an existing yiibaseComponent class without needing to change the class’s inheritance. Attaching a behavior to a component “injects” the behavior’s methods and properties into the component, making those methods and properties accessible as if they were defined in the component class itself. Moreover, a behavior can respond to the events triggered by the component, which allows behaviors to also customize the normal code execution of the component. 5.4.1 Defining Behaviors To define a behavior, create a class that extends yiibaseBehavior, or extends a child class. For example: namespace appcomponents; use yiibaseBehavior; class MyBehavior extends Behavior { public $prop1; private $_prop2; public function getProp2() { return $this->_prop2; } public function setProp2($value) { $this->_prop2 = $value; } public function foo() { // ... } } 2 http://en.wikipedia.org/wiki/Mixin
  • 188. 182 CHAPTER 5. KEY CONCEPTS The above code defines the behavior class appcomponentsMyBehavior, with two properties– prop1 and prop2–and one method foo(). Note that property prop2 is defined via the getter getProp2() and the setter setProp2(). This is the case because yiibaseBehavior extends yiibaseObject, and therefore supports defining properties via getters and setters. Because this class is a behavior, when it is attached to a component, that component will then also have the the prop1 and prop2 properties and the foo() method. Tip: Within a behavior, you can access the component that the behavior is attached to through the yiibaseBehavior::owner property. 5.4.2 Handling Component Events If a behavior needs to respond to the events triggered by the component it is attached to, it should override the yiibaseBehavior::events() method. For example: namespace appcomponents; use yiidbActiveRecord; use yiibaseBehavior; class MyBehavior extends Behavior { // ... public function events() { return [ ActiveRecord::EVENT_BEFORE_VALIDATE => ’beforeValidate’, ]; } public function beforeValidate($event) { // ... } } The yiibaseBehavior::events() method should return a list of events and their corresponding handlers. The above example declares that the yii dbActiveRecord::EVENT_BEFORE_VALIDATE event exists and defines its handler, beforeValidate(). When specifying an event handler, you may use one of the following formats: • a string that refers to the name of a method of the behavior class, like the example above
  • 189. 5.4. BEHAVIORS 183 • an array of an object or class name, and a method name as a string (without parentheses), e.g., [$object, ’methodName’]; • an anonymous function The signature of an event handler should be as follows, where $event refers to the event parameter. Please refer to the Events section for more details about events. function ($event) { } 5.4.3 Attaching Behaviors You can attach a behavior to a yiibaseComponent either statically or dynamically. The former is more common in practice. To attach a behavior statically, override the yiibaseComponent::behaviors() method of the component class to which the behavior is being attached. The yiibaseComponent::behaviors() method should return a list of behavior configurations. Each behavior configuration can be either a behavior class name or a configuration array: namespace appmodels; use yiidbActiveRecord; use appcomponentsMyBehavior; class User extends ActiveRecord { public function behaviors() { return [ // anonymous behavior, behavior class name only MyBehavior::className(), // named behavior, behavior class name only ’myBehavior2’ => MyBehavior::className(), // anonymous behavior, configuration array [ ’class’ => MyBehavior::className(), ’prop1’ => ’value1’, ’prop2’ => ’value2’, ], // named behavior, configuration array ’myBehavior4’ => [ ’class’ => MyBehavior::className(), ’prop1’ => ’value1’, ’prop2’ => ’value2’, ]
  • 190. 184 CHAPTER 5. KEY CONCEPTS ]; } } You may associate a name with a behavior by specifying the array key cor- responding to the behavior configuration. In this case, the behavior is called a named behavior. In the above example, there are two named behaviors: myBehavior2 and myBehavior4. If a behavior is not associated with a name, it is called an anonymous behavior. To attach a behavior dynamically, call the yiibaseComponent::attachBehavior() method of the component to which the behavior is being attached: use appcomponentsMyBehavior; // attach a behavior object $component->attachBehavior(’myBehavior1’, new MyBehavior); // attach a behavior class $component->attachBehavior(’myBehavior2’, MyBehavior::className()); // attach a configuration array $component->attachBehavior(’myBehavior3’, [ ’class’ => MyBehavior::className(), ’prop1’ => ’value1’, ’prop2’ => ’value2’, ]); You may attach multiple behaviors at once using the yiibaseComponent ::attachBehaviors() method: $component->attachBehaviors([ ’myBehavior1’ => new MyBehavior, // a named behavior MyBehavior::className(), // an anonymous behavior ]); You may also attach behaviors through configurations like the following: [ ’as myBehavior2’ => MyBehavior::className(), ’as myBehavior3’ => [ ’class’ => MyBehavior::className(), ’prop1’ => ’value1’, ’prop2’ => ’value2’, ], ] For more details, please refer to the Configurations section. 5.4.4 Using Behaviors To use a behavior, first attach it to a yiibaseComponent per the in- structions above. Once a behavior is attached to a component, its usage is straightforward.
  • 191. 5.4. BEHAVIORS 185 You can access a public member variable or a property defined by a getter and/or a setter of the behavior through the component it is attached to: // "prop1" is a property defined in the behavior class echo $component->prop1; $component->prop1 = $value; You can also call a public method of the behavior similarly: // foo() is a public method defined in the behavior class $component->foo(); As you can see, although $component does not define prop1 and foo(), they can be used as if they are part of the component definition due to the attached behavior. If two behaviors define the same property or method and they are both attached to the same component, the behavior that is attached to the com- ponent first will take precedence when the property or method is accessed. A behavior may be associated with a name when it is attached to a component. If this is the case, you may access the behavior object using the name: $behavior = $component->getBehavior(’myBehavior’); You may also get all behaviors attached to a component: $behaviors = $component->getBehaviors(); 5.4.5 Detaching Behaviors To detach a behavior, call yiibaseComponent::detachBehavior() with the name associated with the behavior: $component->detachBehavior(’myBehavior1’); You may also detach all behaviors: $component->detachBehaviors(); 5.4.6 Using TimestampBehavior To wrap up, let’s take a look at yiibehaviorsTimestampBehavior. This behavior supports automatically updating the timestamp attributes of an yiidbActiveRecord model anytime the model is saved (e.g., on insert or update). First, attach this behavior to the yiidbActiveRecord class that you plan to use: namespace appmodelsUser; use yiidbActiveRecord; use yiibehaviorsTimestampBehavior;
  • 192. 186 CHAPTER 5. KEY CONCEPTS class User extends ActiveRecord { // ... public function behaviors() { return [ [ ’class’ => TimestampBehavior::className(), ’attributes’ => [ ActiveRecord::EVENT_BEFORE_INSERT => [’created_at’, ’ updated_at’], ActiveRecord::EVENT_BEFORE_UPDATE => [’updated_at’], ], ], ]; } } The behavior configuration above specifies that when the record is being: • inserted, the behavior should assign the current timestamp to the created_at and updated_at attributes • updated, the behavior should assign the current timestamp to the updated_at attribute With that code in place, if you have a User object and try to save it, you will find its created_at and updated_at are automatically filled with the current timestamp: $user = new User; $user->email = ’[email protected]’; $user->save(); echo $user->created_at; // shows the current timestamp The yiibehaviorsTimestampBehavior also offers a useful method yii behaviorsTimestampBehavior::touch(), which will assign the current timestamp to a specified attribute and save it to the database: $user->touch(’login_time’); 5.4.7 Comparing Behaviors with Traits While behaviors are similar to traits3 in that they both “inject” their prop- erties and methods to the primary class, they differ in many aspects. As explained below, they both have pros and cons. They are more like comple- ments to each other rather than alternatives. 3 http://www.php.net/traits
  • 193. 5.5. CONFIGURATIONS 187 Reasons to Use Behaviors Behavior classes, like normal classes, support inheritance. Traits, on the other hand, can be considered as language-supported copy and paste. They do not support inheritance. Behaviors can be attached and detached to a component dynamically without requiring modification of the component class. To use a trait, you must modify the class using it. Behaviors are configurable while traits are not. Behaviors can customize the code execution of a component by respond- ing to its events. When there can be name conflicts among different behaviors attached to the same component, the conflicts are automatically resolved by prioritizing the behavior attached to the component first. Name conflicts caused by dif- ferent traits requires manually resolution by renaming the affected properties or methods. Reasons to Use Traits Traits are much more efficient than behaviors as behaviors are objects that take both time and memory. IDEs are more friendly to traits as they are language construct. 5.5 Configurations Configurations are widely used in Yii when creating new objects or initial- izing existing objects. Configurations usually include the class name of the object being created, and a list of initial values that should be assigned to the object’s properties. Configurations may also include a list of handlers that should be attached to the object’s events and/or a list of behaviors that should also be attached to the object. In the following, a configuration is used to create and initialize a database connection: $config = [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ]; $db = Yii::createObject($config); The Yii::createObject() method takes a configuration array as its argu- ment, and creates an object by instantiating the class named in the config-
  • 194. 188 CHAPTER 5. KEY CONCEPTS uration. When the object is instantiated, the rest of the configuration will be used to initialize the object’s properties, event handlers, and behaviors. If you already have an object, you may use Yii::configure() to initial- ize the object’s properties with a configuration array: Yii::configure($object, $config); Note that, in this case, the configuration array should not contain a class element. 5.5.1 Configuration Format The format of a configuration can be formally described as: [ ’class’ => ’ClassName’, ’propertyName’ => ’propertyValue’, ’on eventName’ => $eventHandler, ’as behaviorName’ => $behaviorConfig, ] where • The class element specifies a fully qualified class name for the object being created. • The propertyName elements specify the initial values for the named prop- erty. The keys are the property names, and the values are the corres- ponding initial values. Only public member variables and properties defined by getters/setters can be configured. • The on eventName elements specify what handlers should be attached to the object’s events. Notice that the array keys are formed by prefixing event names with on . Please refer to the Events section for supported event handler formats. • The as behaviorName elements specify what behaviors should be at- tached to the object. Notice that the array keys are formed by pre- fixing behavior names with as ; the value, $behaviorConfig, represents the configuration for creating a behavior, like a normal configuration described here. Below is an example showing a configuration with initial property values, event handlers, and behaviors: [ ’class’ => ’appcomponentsSearchEngine’, ’apiKey’ => ’xxxxxxxx’, ’on search’ => function ($event) { Yii::info("Keyword searched: " . $event->keyword); },
  • 195. 5.5. CONFIGURATIONS 189 ’as indexer’ => [ ’class’ => ’appcomponentsIndexerBehavior’, // ... property init values ... ], ] 5.5.2 Using Configurations Configurations are used in many places in Yii. At the beginning of this section, we have shown how to create an object according to a configura- tion by using Yii::createObject(). In this subsection, we will describe application configurations and widget configurations - two major usages of configurations. Application Configurations Configuration for an application is probably one of the most complex config- urations. This is because the yiiwebApplication class has a lot of config- urable properties and events. More importantly, its yiiwebApplication ::components property can receive an array of configurations for creating components that are registered through the application. The following is an abstract from the application configuration file for the basic application template. $config = [ ’id’ => ’basic’, ’basePath’ => dirname(__DIR__), ’extensions’ => require(__DIR__ . ’/../vendor/yiisoft/extensions.php’), ’components’ => [ ’cache’ => [ ’class’ => ’yiicachingFileCache’, ], ’mailer’ => [ ’class’ => ’yiiswiftmailerMailer’, ], ’log’ => [ ’class’ => ’yiilogDispatcher’, ’traceLevel’ => YII_DEBUG ? 3 : 0, ’targets’ => [ [ ’class’ => ’yiilogFileTarget’, ], ], ], ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=stay2’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’,
  • 196. 190 CHAPTER 5. KEY CONCEPTS ], ], ]; The configuration does not have a class key. This is because it is used as follows in an entry script, where the class name is already given, (new yiiwebApplication($config))->run(); For more details about configuring the components property of an application can be found in the Applications section and the Service Locator section. Widget Configurations When using widgets, you often need to use configurations to customize the widget properties. Both of the yiibaseWidget::widget() and yiibase Widget::begin() methods can be used to create a widget. They take a configuration array, like the following, use yiiwidgetsMenu; echo Menu::widget([ ’activateItems’ => false, ’items’ => [ [’label’ => ’Home’, ’url’ => [’site/index’]], [’label’ => ’Products’, ’url’ => [’product/index’]], [’label’ => ’Login’, ’url’ => [’site/login’], ’visible’ => Yii::$app ->user->isGuest], ], ]); The above code creates a Menu widget and initializes its activeItems property to be false. The items property is also configured with menu items to be displayed. Note that because the class name is already given, the configuration array should NOT have the class key. 5.5.3 Configuration Files When a configuration is very complex, a common practice is to store it in one or multiple PHP files, known as configuration files. A configuration file returns a PHP array representing the configuration. For example, you may keep an application configuration in a file named web.php, like the following, return [ ’id’ => ’basic’, ’basePath’ => dirname(__DIR__), ’extensions’ => require(__DIR__ . ’/../vendor/yiisoft/extensions.php’), ’components’ => require(__DIR__ . ’/components.php’), ];
  • 197. 5.5. CONFIGURATIONS 191 Because the components configuration is complex too, you store it in a separate file called components.php and “require” this file in web.php as shown above. The content of components.php is as follows, return [ ’cache’ => [ ’class’ => ’yiicachingFileCache’, ], ’mailer’ => [ ’class’ => ’yiiswiftmailerMailer’, ], ’log’ => [ ’class’ => ’yiilogDispatcher’, ’traceLevel’ => YII_DEBUG ? 3 : 0, ’targets’ => [ [ ’class’ => ’yiilogFileTarget’, ], ], ], ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=stay2’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ], ]; To get a configuration stored in a configuration file, simply “require” it, like the following: $config = require(’path/to/web.php’); (new yiiwebApplication($config))->run(); 5.5.4 Default Configurations The Yii::createObject() method is implemented based on a dependency injection container. It allows you to specify a set of the so-called default con- figurations which will be applied to ANY instances of the specified classes when they are being created using Yii::createObject(). The default con- figurations can be specified by calling Yii::$container->set() in the boot- strapping code. For example, if you want to customize yiiwidgetsLinkPager so that ALL link pagers will show at most 5 page buttons (the default value is 10), you may use the following code to achieve this goal, Yii::$container->set(’yiiwidgetsLinkPager’, [ ’maxButtonCount’ => 5, ]); Without using default configurations, you would have to configure maxButtonCount in every place where you use link pagers.
  • 198. 192 CHAPTER 5. KEY CONCEPTS 5.5.5 Environment Constants Configurations often vary according to the environment in which an applic- ation runs. For example, in development environment, you may want to use a database named mydb_dev, while on production server you may want to use the mydb_prod database. To facilitate switching environments, Yii provides a constant named YII_ENV that you may define in the entry script of your application. For example, defined(’YII_ENV’) or define(’YII_ENV’, ’dev’); You may define YII_ENV as one of the following values: • prod: production environment. The constant YII_ENV_PROD will evaluate as true. This is the default value of YII_ENV if you do not define it. • dev: development environment. The constant YII_ENV_DEV will evaluate as true. • test: testing environment. The constant YII_ENV_TEST will evaluate as true. With these environment constants, you may specify your configurations con- ditionally based on the current environment. For example, your application configuration may contain the following code to enable the debug toolbar and debugger in development environment. $config = [...]; if (YII_ENV_DEV) { // configuration adjustments for ’dev’ environment $config[’bootstrap’][] = ’debug’; $config[’modules’][’debug’] = ’yiidebugModule’; } return $config; 5.6 Aliases Aliases are used to represent file paths or URLs so that you don’t have to hard-code absolute paths or URLs in your project. An alias must start with the @ character to be differentiated from normal file paths and URLs. Yii has many pre-defined aliases already available. For example, the alias @yii represents the installation path of the Yii framework; @web represents the base URL for the currently running Web application.
  • 199. 5.6. ALIASES 193 5.6.1 Defining Aliases You can define an alias for a file path or URL by calling Yii::setAlias(): // an alias of a file path Yii::setAlias(’@foo’, ’/path/to/foo’); // an alias of a URL Yii::setAlias(’@bar’, ’http://www.example.com’); Note: The file path or URL being aliased may not necessarily refer to an existing file or resource. Given a defined alias, you may derive a new alias (without the need of calling Yii::setAlias()) by appending a slash / followed with one or more path segments. The aliases defined via Yii::setAlias() becomes the root alias, while aliases derived from it are derived aliases. For example, @foo is a root alias, while @foo/bar/file.php is a derived alias. You can define an alias using another alias (either root or derived): Yii::setAlias(’@foobar’, ’@foo/bar’); Root aliases are usually defined during the bootstrapping stage. For ex- ample, you may call Yii::setAlias() in the entry script. For convenience, Application provides a writable property named aliases that you can con- figure in the application configuration: return [ // ... ’aliases’ => [ ’@foo’ => ’/path/to/foo’, ’@bar’ => ’http://www.example.com’, ], ]; 5.6.2 Resolving Aliases You can call Yii::getAlias() to resolve a root alias into the file path or URL it represents. The same method can also resolve a derived alias into the corresponding file path or URL: echo Yii::getAlias(’@foo’); // displays: /path/to/foo echo Yii::getAlias(’@bar’); // displays: http://www.example. com echo Yii::getAlias(’@foo/bar/file.php’); // displays: /path/to/foo/bar/file .php The path/URL represented by a derived alias is determined by replacing the root alias part with its corresponding path/URL in the derived alias. Note: The Yii::getAlias() method does not check whether the resulting path/URL refers to an existing file or resource.
  • 200. 194 CHAPTER 5. KEY CONCEPTS A root alias may also contain slash / characters. The Yii::getAlias() method is intelligent enough to tell which part of an alias is a root alias and thus correctly determines the corresponding file path or URL: Yii::setAlias(’@foo’, ’/path/to/foo’); Yii::setAlias(’@foo/bar’, ’/path2/bar’); Yii::getAlias(’@foo/test/file.php’); // displays: /path/to/foo/test/file. php Yii::getAlias(’@foo/bar/file.php’); // displays: /path2/bar/file.php If @foo/bar is not defined as a root alias, the last statement would display /path/to/foo/bar/file.php. 5.6.3 Using Aliases Aliases are recognized in many places in Yii without needing to call Yii:: getAlias() to convert them into paths or URLs. For example, yiicaching FileCache::cachePath can accept both a file path and an alias represent- ing a file path, thanks to the @ prefix which allows it to differentiate a file path from an alias. use yiicachingFileCache; $cache = new FileCache([ ’cachePath’ => ’@runtime/cache’, ]); Please pay attention to the API documentation to see if a property or method parameter supports aliases. 5.6.4 Predefined Aliases Yii predefines a set of aliases to easily reference commonly used file paths and URLs: • @yii, the directory where the BaseYii.php file is located (also called the framework directory) • @app, the yiibaseApplication::basePath of the currently running application • @runtime, the yiibaseApplication::runtimePath of the currently running application. Defaults to @app/runtime. • @webroot, the Web root directory of the currently running Web applic- ation. It is determined based on the directory containing the entry script. • @web, the base URL of the currently running Web application. It has the same value as yiiwebRequest::baseUrl.
  • 201. 5.7. CLASS AUTOLOADING 195 • @vendor, the yiibaseApplication::vendorPath. Defaults to @app/ vendor. • @bower, the root directory that contains bower packages4. Defaults to @vendor/bower. • @npm, the root directory that contains npm packages5. Defaults to @vendor/npm. The @yii alias is defined when you include the Yii.php file in your entry script. The rest of the aliases are defined in the application constructor when applying the application configuration. 5.6.5 Extension Aliases An alias is automatically defined for each extension that is installed via Composer. Each alias is named after the root namespace of the extension as declared in its composer.json file, and each alias represents the root directory of the package. For example, if you install the yiisoft/yii2-jui extension, you will automatically have the alias @yii/jui defined during the bootstrapping stage, equivalent to: Yii::setAlias(’@yii/jui’, ’VendorPath/yiisoft/yii2-jui’); 5.7 Class Autoloading Yii relies on the class autoloading mechanism6 to locate and include all required class files. It provides a high-performance class autoloader that is compliant to the PSR-4 standard7. The autoloader is installed when you include the Yii.php file. Note: For simplicity of description, in this section we will only talk about autoloading of classes. However, keep in mind that the content we are describing here applies to autoloading of interfaces and traits as well. 5.7.1 Using the Yii Autoloader To make use of the Yii class autoloader, you should follow two simple rules when creating and naming your classes: 4 http://bower.io/ 5 https://www.npmjs.org/ 6 http://www.php.net/manual/en/language.oop5.autoload.php 7 https://github.com/php-fig/fig-standards/blob/master/proposed/ psr-4-autoloader/psr-4-autoloader.md
  • 202. 196 CHAPTER 5. KEY CONCEPTS • Each class must be under a namespace (e.g. foobarMyClass) • Each class must be saved in an individual file whose path is determined by the following algorithm: // $className is a fully qualified class name with the leading backslash $classFile = Yii::getAlias(’@’ . str_replace(’’, ’/’, $className) . ’.php’ ); For example, if a class name and namespace is foobarMyClass, the alias for the corresponding class file path would be @foo/bar/MyClass.php. In order for this alias to be resolvable into a file path, either @foo or @foo/bar must be a root alias. When using the Basic Application Template, you may put your classes under the top-level namespace app so that they can be autoloaded by Yii without the need of defining a new alias. This is because @app is a predefined alias, and a class name like appcomponentsMyClass can be resolved into the class file AppBasePath/components/MyClass.php, according to the algorithm just described. In the Advanced Application Template, each tier has its own root alias. For example, the front-end tier has a root alias @frontend, while the back- end tier @backend. As a result, you may put the front-end classes under the namespace frontend while the back-end classes are under backend. This will allow these classes to be autoloaded by the Yii autoloader. 5.7.2 Class Map The Yii class autoloader supports the class map feature, which maps class names to the corresponding class file paths. When the autoloader is loading a class, it will first check if the class is found in the map. If so, the corres- ponding file path will be included directly without further check. This makes class autoloading super fast. In fact, all core Yii classes are autoloaded this way. You may add a class to the class map, stored in Yii::$classMap, using: Yii::$classMap[’foobarMyClass’] = ’path/to/MyClass.php’; Aliases can be used to specify class file paths. You should set the class map in the bootstrapping process so that the map is ready before your classes are used. 5.7.3 Using Other Autoloaders Because Yii embraces Composer as a package dependency manager, it is recommended that you also install the Composer autoloader. If you are using 3rd-party libraries that have their own autoloaders, you should also install those.
  • 203. 5.8. SERVICE LOCATOR 197 When using the Yii autoloader together with other autoloaders, you should include the Yii.php file after all other autoloaders are installed. This will make the Yii autoloader the first one responding to any class autoloading request. For example, the following code is extracted from the entry script of the Basic Application Template. The first line installs the Composer autoloader, while the second line installs the Yii autoloader: require(__DIR__ . ’/../vendor/autoload.php’); require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’); You may use the Composer autoloader alone without the Yii autoloader. However, by doing so, the performance of your class autoloading may be degraded, and you must follow the rules set by Composer in order for your classes to be autoloadable. Info: If you do not want to use the Yii autoloader, you must create your own version of the Yii.php file and include it in your entry script. 5.7.4 Autoloading Extension Classes The Yii autoloader is capable of autoloading extension classes. The sole requirement is that an extension specifies the autoload section correctly in its composer.json file. Please refer to the Composer documentation8 for more details about specifying autoload. In case you do not use the Yii autoloader, the Composer autoloader can still autoload extension classes for you. 5.8 Service Locator A service locator is an object that knows how to provide all sorts of services (or components) that an application might need. Within a service locator, each component exists as only a single instance, uniquely identified by an ID. You use the ID to retrieve a component from the service locator. In Yii, a service locator is simply an instance of yiidiServiceLocator, or from a child class. The most commonly used service locator in Yii is the application ob- ject, which can be accessed through Yii::$app. The services it provides are called application components, such as the request, response, and urlManager components. You may configure these components, or even replace them with your own implementations, easily through functionality provided by the service locator. Besides the application object, each module object is also a service loc- ator. 8 https://getcomposer.org/doc/04-schema.md#autoload
  • 204. 198 CHAPTER 5. KEY CONCEPTS To use a service locator, the first step is to register components with it. A component can be registered via yiidiServiceLocator::set(). The following code shows different ways of registering components: use yiidiServiceLocator; use yiicachingFileCache; $locator = new ServiceLocator; // register "cache" using a class name that can be used to create a component $locator->set(’cache’, ’yiicachingApcCache’); // register "db" using a configuration array that can be used to create a component $locator->set(’db’, [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ]); // register "search" using an anonymous function that builds a component $locator->set(’search’, function () { return new appcomponentsSolrService; }); // register "pageCache" using a component $locator->set(’pageCache’, new FileCache); Once a component has been registered, you can access it using its ID, in one of the two following ways: $cache = $locator->get(’cache’); // or alternatively $cache = $locator->cache; As shown above, yiidiServiceLocator allows you to access a component like a property using the component ID. When you access a component for the first time, yiidiServiceLocator will use the component registration information to create a new instance of the component and return it. Later, if the component is accessed again, the service locator will return the same instance. You may use yiidiServiceLocator::has() to check if a component ID has already been registered. If you call yiidiServiceLocator::get() with an invalid ID, an exception will be thrown. Because service locators are often being created with configurations, a writable property named yiidiServiceLocator::setComponents() is provided. This allows you to configure and register multiple components at once. The following code shows a configuration array that can be used to configure an application, while also registering the “db”, “cache” and “search” components:
  • 205. 5.9. DEPENDENCY INJECTION CONTAINER 199 return [ // ... ’components’ => [ ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ], ’cache’ => ’yiicachingApcCache’, ’search’ => function () { return new appcomponentsSolrService; }, ], ]; 5.9 Dependency Injection Container A dependency injection (DI) container is an object that knows how to in- stantiate and configure objects and all their dependent objects. Martin’s article9 has well explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii. 5.9.1 Dependency Injection Yii provides the DI container feature through the class yiidiContainer. It supports the following kinds of dependency injection: • Constructor injection; • Setter and property injection; • PHP callable injection. Constructor Injection The DI container supports constructor injection with the help of type hints for constructor parameters. The type hints tell the container which classes or interfaces are dependent when it is used to create a new object. The container will try to get the instances of the dependent classes or interfaces and then inject them into the new object through the constructor. For example, class Foo { public function __construct(Bar $bar) { 9 http://martinfowler.com/articles/injection.html
  • 206. 200 CHAPTER 5. KEY CONCEPTS } } $foo = $container->get(’Foo’); // which is equivalent to the following: $bar = new Bar; $foo = new Foo($bar); Setter and Property Injection Setter and property injection is supported through configurations. When registering a dependency or when creating a new object, you can provide a configuration which will be used by the container to inject the dependencies through the corresponding setters or properties. For example, use yiibaseObject; class Foo extends Object { public $bar; private $_qux; public function getQux() { return $this->_qux; } public function setQux(Qux $qux) { $this->_qux = $qux; } } $container->get(’Foo’, [], [ ’bar’ => $container->get(’Bar’), ’qux’ => $container->get(’Qux’), ]); PHP Callable Injection In this case, the container will use a registered PHP callable to build new instances of a class. The callable is responsible to resolve the dependencies and inject them appropriately to the newly created objects. For example, $container->set(’Foo’, function () { return new Foo(new Bar); }); $foo = $container->get(’Foo’);
  • 207. 5.9. DEPENDENCY INJECTION CONTAINER 201 5.9.2 Registering Dependencies You can use yiidiContainer::set() to register dependencies. The re- gistration requires a dependency name as well as a dependency definition. A dependency name can be a class name, an interface name, or an alias name; and a dependency definition can be a class name, a configuration array, or a PHP callable. $container = new yiidiContainer; // register a class name as is. This can be skipped. $container->set(’yiidbConnection’); // register an interface // When a class depends on the interface, the corresponding class // will be instantiated as the dependent object $container->set(’yiimailMailInterface’, ’yiiswiftmailerMailer’); // register an alias name. You can use $container->get(’foo’) // to create an instance of Connection $container->set(’foo’, ’yiidbConnection’); // register a class with configuration. The configuration // will be applied when the class is instantiated by get() $container->set(’yiidbConnection’, [ ’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ]); // register an alias name with class configuration // In this case, a "class" element is required to specify the class $container->set(’db’, [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ]); // register a PHP callable // The callable will be executed each time when $container->get(’db’) is called $container->set(’db’, function ($container, $params, $config) { return new yiidbConnection($config); }); // register a component instance // $container->get(’pageCache’) will return the same instance each time it is called $container->set(’pageCache’, new FileCache);
  • 208. 202 CHAPTER 5. KEY CONCEPTS Tip: If a dependency name is the same as the corresponding dependency definition, you do not need to register it with the DI container. A dependency registered via set() will generate an instance each time the dependency is needed. You can use yiidiContainer::setSingleton() to register a dependency that only generates a single instance: $container->setSingleton(’yiidbConnection’, [ ’dsn’ => ’mysql:host=127.0.0.1;dbname=demo’, ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ]); 5.9.3 Resolving Dependencies Once you have registered dependencies, you can use the DI container to create new objects, and the container will automatically resolve dependencies by instantiating them and injecting them into the newly created objects. The dependency resolution is recursive, meaning that if a dependency has other dependencies, those dependencies will also be resolved automatically. You can use yiidiContainer::get() to create new objects. The method takes a dependency name, which can be a class name, an inter- face name or an alias name. The dependency name may or may not be registered via set() or setSingleton(). You may optionally provide a list of class constructor parameters and a configuration to configure the newly created object. For example, // "db" is a previously registered alias name $db = $container->get(’db’); // equivalent to: $engine = new appcomponentsSearchEngine($apiKey, [’type ’ => 1]); $engine = $container->get(’appcomponentsSearchEngine’, [$apiKey], [’type’ => 1]); Behind the scene, the DI container does much more work than just creating a new object. The container will first inspect the class constructor to find out dependent class or interface names and then automatically resolve those dependencies recursively. The following code shows a more sophisticated example. The UserLister class depends on an object implementing the UserFinderInterface interface; the UserFinder class implements this interface and depends on a Connection object. All these dependencies are declared through type hinting of the class constructor parameters. With property dependency registration, the DI container is able to resolve these dependencies automatically and creates a new UserLister instance with a simple call of get(’userLister’).
  • 209. 5.9. DEPENDENCY INJECTION CONTAINER 203 namespace appmodels; use yiibaseObject; use yiidbConnection; use yiidiContainer; interface UserFinderInterface { function findUser(); } class UserFinder extends Object implements UserFinderInterface { public $db; public function __construct(Connection $db, $config = []) { $this->db = $db; parent::__construct($config); } public function findUser() { } } class UserLister extends Object { public $finder; public function __construct(UserFinderInterface $finder, $config = []) { $this->finder = $finder; parent::__construct($config); } } $container = new Container; $container->set(’yiidbConnection’, [ ’dsn’ => ’...’, ]); $container->set(’appmodelsUserFinderInterface’, [ ’class’ => ’appmodelsUserFinder’, ]); $container->set(’userLister’, ’appmodelsUserLister’); $lister = $container->get(’userLister’); // which is equivalent to: $db = new yiidbConnection([’dsn’ => ’...’]); $finder = new UserFinder($db); $lister = new UserLister($finder);
  • 210. 204 CHAPTER 5. KEY CONCEPTS 5.9.4 Practical Usage Yii creates a DI container when you include the Yii.php file in the entry script of your application. The DI container is accessible via Yii::$container. When you call Yii::createObject(), the method will actually call the con- tainer’s yiidiContainer::get() method to create a new object. As afore- mentioned, the DI container will automatically resolve the dependencies (if any) and inject them into the newly created object. Because Yii uses Yii:: createObject() in most of its core code to create new objects, this means you can customize the objects globally by dealing with Yii::$container. For example, you can customize globally the default number of pagination buttons of yiiwidgetsLinkPager: Yii::$container->set(’yiiwidgetsLinkPager’, [’maxButtonCount’ => 5]); Now if you use the widget in a view with the following code, the maxButtonCount property will be initialized as 5 instead of the default value 10 as defined in the class. echo yiiwidgetsLinkPager::widget(); You can still override the value set via DI container, though: echo yiiwidgetsLinkPager::widget([’maxButtonCount’ => 20]); Another example is to take advantage of the automatic constructor injection of the DI container. Assume your controller class depends on some other objects, such as a hotel booking service. You can declare the dependency through a constructor parameter and let the DI container to resolve it for you. namespace appcontrollers; use yiiwebController; use appcomponentsBookingInterface; class HotelController extends Controller { protected $bookingService; public function __construct($id, $module, BookingInterface $bookingService, $config = []) { $this->bookingService = $bookingService; parent::__construct($id, $module, $config); } } If you access this controller from browser, you will see an error complaining the BookingInterface cannot be instantiated. This is because you need to tell the DI container how to deal with this dependency: Yii::$container->set(’appcomponentsBookingInterface’, ’appcomponents BookingService’);
  • 211. 5.9. DEPENDENCY INJECTION CONTAINER 205 Now if you access the controller again, an instance of appcomponentsBookingService will be created and injected as the 3rd parameter to the controller’s con- structor. 5.9.5 When to Register Dependencies Because dependencies are needed when new objects are being created, their registration should be done as early as possible. The followings are the recommended practices: • If you are the developer of an application, you can register dependencies in your application’s entry script or in a script that is included by the entry script. • If you are the developer of a redistributable extension, you can register dependencies in the bootstrapping class of the extension. 5.9.6 Summary Both dependency injection and service locator are popular design patterns that allow building software in a loosely-coupled and more testable fash- ion. We highly recommend you to read Martin’s article10 to get a deeper understanding of dependency injection and service locator. Yii implements its service locator on top of the dependency injection (DI) container. When a service locator is trying to create a new object instance, it will forward the call to the DI container. The latter will resolve the dependencies automatically as described above. 10 http://martinfowler.com/articles/injection.html
  • 212. 206 CHAPTER 5. KEY CONCEPTS
  • 213. Chapter 6 Working with Databases 6.1 Database basics Note: This section is under development. Yii has a database access layer built on top of PHP’s PDO1. It provides uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS: • MySQL2 • MariaDB3 • SQLite4 • PostgreSQL5 • CUBRID6: version 9.1.0 or higher. • Oracle7 • MSSQL8: version 2005 or higher. 6.1.1 Configuration In order to start using database you need to configure database connection component first by adding db component to application configuration (for “basic” web application it’s config/web.php) like the following: 1 http://www.php.net/manual/en/book.pdo.php 2 http://www.mysql.com/ 3 https://mariadb.com/ 4 http://sqlite.org/ 5 http://www.postgresql.org/ 6 http://www.cubrid.org/ 7 http://www.oracle.com/us/products/database/overview/index.html 8 https://www.microsoft.com/en-us/sqlserver/default.aspx 207
  • 214. 208 CHAPTER 6. WORKING WITH DATABASES return [ // ... ’components’ => [ // ... ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=mydatabase’, // MySQL, MariaDB //’dsn’ => ’sqlite:/path/to/database/file’, // SQLite //’dsn’ => ’pgsql:host=localhost;port=5432;dbname=mydatabase’, // PostgreSQL //’dsn’ => ’cubrid:dbname=demodb;host=localhost;port=33000’, // CUBRID //’dsn’ => ’sqlsrv:Server=localhost;Database=mydatabase’, // MS SQL Server, sqlsrv driver //’dsn’ => ’dblib:host=localhost;dbname=mydatabase’, // MS SQL Server, dblib driver //’dsn’ => ’mssql:host=localhost;dbname=mydatabase’, // MS SQL Server, mssql driver //’dsn’ => ’oci:dbname=//localhost:1521/mydatabase’, // Oracle ’username’ => ’root’, ’password’ => ’’, ’charset’ => ’utf8’, ], ], // ... ]; There is a peculiarity when you want to work with the database through the ODBC layer. When using ODBC, connection DSN doesn’t indicate uniquely what database type is being used. That’s why you have to override driverName property of yiidbConnection class to disambiguate that: ’db’ => [ ’class’ => ’yiidbConnection’, ’driverName’ => ’mysql’, ’dsn’ => ’odbc:Driver={MySQL};Server=localhost;Database=test’, ’username’ => ’root’, ’password’ => ’’, ], Please refer to the PHP manual9 for more details on the format of the DSN string. After the connection component is configured you can access it using the following syntax: $connection = Yii::$app->db; You can refer to yiidbConnection for a list of properties you can con- figure. Also note that you can define more than one connection component and use both at the same time if needed: 9 http://www.php.net/manual/en/function.PDO-construct.php
  • 215. 6.1. DATABASE BASICS 209 $primaryConnection = Yii::$app->db; $secondaryConnection = Yii::$app->secondDb; If you don’t want to define the connection as an application component you can instantiate it directly: $connection = new yiidbConnection([ ’dsn’ => $dsn, ’username’ => $username, ’password’ => $password, ]); $connection->open(); Tip: if you need to execute additional SQL queries right after establishing a connection you can add the following to your ap- plication configuration file: return [ // ... ’components’ => [ // ... ’db’ => [ ’class’ => ’yiidbConnection’, // ... ’on afterOpen’ => function($event) { $event->sender->createCommand("SET time_zone = ’ UTC’")->execute(); } ], ], // ... ]; 6.1.2 Basic SQL queries Once you have a connection instance you can execute SQL queries using yii dbCommand. SELECT When query returns a set of rows: $command = $connection->createCommand(’SELECT * FROM post’); $posts = $command->queryAll(); When only a single row is returned: $command = $connection->createCommand(’SELECT * FROM post WHERE id=1’); $post = $command->queryOne(); When there are multiple values from the same column: $command = $connection->createCommand(’SELECT title FROM post’); $titles = $command->queryColumn();
  • 216. 210 CHAPTER 6. WORKING WITH DATABASES When there’s a scalar value: $command = $connection->createCommand(’SELECT COUNT(*) FROM post’); $postCount = $command->queryScalar(); UPDATE, INSERT, DELETE etc. If SQL executed doesn’t return any data you can use command’s execute method: $command = $connection->createCommand(’UPDATE post SET status=1 WHERE id=1’) ; $command->execute(); Alternatively the following syntax that takes care of proper table and column names quoting is possible: // INSERT $connection->createCommand()->insert(’user’, [ ’name’ => ’Sam’, ’age’ => 30, ])->execute(); // INSERT multiple rows at once $connection->createCommand()->batchInsert(’user’, [’name’, ’age’], [ [’Tom’, 30], [’Jane’, 20], [’Linda’, 25], ])->execute(); // UPDATE $connection->createCommand()->update(’user’, [’status’ => 1], ’age > 30’)-> execute(); // DELETE $connection->createCommand()->delete(’user’, ’status = 0’)->execute(); 6.1.3 Quoting table and column names Most of the time you would use the following syntax for quoting table and column names: $sql = "SELECT COUNT([[$column]]) FROM {{table}}"; $rowCount = $connection->createCommand($sql)->queryScalar(); In the code above [[X]] will be converted to properly quoted column name while {{Y}} will be converted to properly quoted table name. For table names there’s a special variant {{%Y}} that allows you to auto- matically appending table prefix if it is set: $sql = "SELECT COUNT([[$column]]) FROM {{%table}}"; $rowCount = $connection->createCommand($sql)->queryScalar();
  • 217. 6.1. DATABASE BASICS 211 The code above will result in selecting from tbl_table if you have table prefix configured like the following in your config file: return [ // ... ’components’ => [ // ... ’db’ => [ // ... ’tablePrefix’ => ’tbl_’, ], ], ]; The alternative is to quote table and column names manually using yiidb Connection::quoteTableName() and yiidbConnection::quoteColumnName(): $column = $connection->quoteColumnName($column); $table = $connection->quoteTableName($table); $sql = "SELECT COUNT($column) FROM $table"; $rowCount = $connection->createCommand($sql)->queryScalar(); 6.1.4 Prepared statements In order to securely pass query parameters you can use prepared statements: $command = $connection->createCommand(’SELECT * FROM post WHERE id=:id’); $command->bindValue(’:id’, $_GET[’id’]); $post = $command->query(); Another usage is performing a query multiple times while preparing it only once: $command = $connection->createCommand(’DELETE FROM post WHERE id=:id’); $command->bindParam(’:id’, $id); $id = 1; $command->execute(); $id = 2; $command->execute(); 6.1.5 Transactions When running multiple related queries in a sequence you may need to wrap them in a transaction to ensure you data is consistent. Yii provides a simple interface to work with transactions in simple cases but also for advanced usage when you need to define isolation levels. The following code shows a simple pattern that all code that uses trans- actional queries should follow:
  • 218. 212 CHAPTER 6. WORKING WITH DATABASES $transaction = $connection->beginTransaction(); try { $connection->createCommand($sql1)->execute(); $connection->createCommand($sql2)->execute(); // ... executing other SQL statements ... $transaction->commit(); } catch(Exception $e) { $transaction->rollBack(); throw $e; } The first line starts a new transaction using the yiidbConnection::beginTransaction()- method of the database connection object. The transaction itself is repres- ented by a yiidbTransaction object stored in $transaction. We wrap the execution of all queries in a try-catch-block to be able to handle errors. We call yiidbTransaction::commit() on success to commit the transaction and yiidbTransaction::rollBack() in case of an error. This will revert the effect of all queries that have been executed inside of the transaction. throw $e is used to re-throw the exception in case we can not handle the error ourselves and delegate it to some other code or the yii error handler. It is also possible to nest multiple transactions, if needed: // outer transaction $transaction1 = $connection->beginTransaction(); try { $connection->createCommand($sql1)->execute(); // inner transaction $transaction2 = $connection->beginTransaction(); try { $connection->createCommand($sql2)->execute(); $transaction2->commit(); } catch (Exception $e) { $transaction2->rollBack(); } $transaction1->commit(); } catch (Exception $e) { $transaction1->rollBack(); } Note that your DBMS should have support for Savepoints for this to work as expected. The above code will work for any DBMS but transactional safety is only guaranteed if the underlying DBMS supports it. Yii also supports setting isolation levels10 for your transactions. When beginning a transaction it will run in the default isolation level set by you database system. You can specifying an isolation level explicitly when start- ing a transaction: 10 http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_ levels
  • 219. 6.1. DATABASE BASICS 213 $transaction = $connection->beginTransaction(yiidbTransaction:: REPEATABLE_READ); Yii provides four constants for the most common isolation levels: • yiidbTransaction::READ_UNCOMMITTED - the weakest level, Dirty reads, Non-repeatable reads and Phantoms may occur. • yiidbTransaction::READ_COMMITTED - avoid Dirty reads. • yiidbTransaction::REPEATABLE_READ - avoid Dirty reads and Non- repeatable reads. • yiidbTransaction::SERIALIZABLE - the strongest level, avoids all of the above named problems. You may use the constants named above but you can also use a string that represents a valid syntax that can be used in your DBMS follow- ing SET TRANSACTION ISOLATION LEVEL. For postgres this could be for example SERIALIZABLE READ ONLY DEFERRABLE. Note that some DBMS allow setting of the isolation level only for the whole connection so subsequent transactions may get the same isolation level even if you did not specify any. When using this feature you may need to set the isolation level for all transactions explicitly to avoid conflicting settings. At the time of this writing affected DBMS are MSSQL and SQLite. Note: SQLite only supports two isolation levels, so you can only use READ UNCOMMITTED and SERIALIZABLE. Usage of other levels will result in an exception to be thrown. Note: PostgreSQL does not allow setting the isolation level before the transaction starts so you can not specify the isolation level directly when starting the transaction. You have to call yii dbTransaction::setIsolationLevel() in this case after the transaction has started. 6.1.6 Replication and Read-Write Splitting Many DBMS support database replication11 to get better database availab- ility and faster server response time. With database replication, data are replicated from the so-called master servers to slave servers. All writes and updates must take place on the master servers, while reads may take place on the slave servers. To take advantage of database replication and achieve read-write split- ting, you can configure a yiidbConnection component like the following: 11 http://en.wikipedia.org/wiki/Replication_(computing)#Database_ replication
  • 220. 214 CHAPTER 6. WORKING WITH DATABASES [ ’class’ => ’yiidbConnection’, // configuration for the master ’dsn’ => ’dsn for master server’, ’username’ => ’master’, ’password’ => ’’, // common configuration for slaves ’slaveConfig’ => [ ’username’ => ’slave’, ’password’ => ’’, ’attributes’ => [ // use a smaller connection timeout PDO::ATTR_TIMEOUT => 10, ], ], // list of slave configurations ’slaves’ => [ [’dsn’ => ’dsn for slave server 1’], [’dsn’ => ’dsn for slave server 2’], [’dsn’ => ’dsn for slave server 3’], [’dsn’ => ’dsn for slave server 4’], ], ] The above configuration specifies a setup with a single master and multiple slaves. One of the slaves will be connected and used to perform read queries, while the master will be used to perform write queries. Such read-write splitting is accomplished automatically with this configuration. For example, // create a Connection instance using the above configuration $db = Yii::createObject($config); // query against one of the slaves $rows = $db->createCommand(’SELECT * FROM user LIMIT 10’)->queryAll(); // query against the master $db->createCommand("UPDATE user SET username=’demo’ WHERE id=1")->execute(); Info: Queries performed by calling yiidbCommand::execute() are considered as write queries, while all other queries done through one of the “query” method of yiidbCommand are read queries. You can get the currently active slave connection via $db->slave. The Connection component supports load balancing and failover about slaves. When performing a read query for the first time, the Connection component will randomly pick a slave and try connecting to it. If the slave is found “dead”, it will try another one. If none of the slaves is available, it will connect to the master. By configuring a yiidbConnection::serverStatusCache,
  • 221. 6.1. DATABASE BASICS 215 a “dead” server can be remembered so that it will not be tried again during a yiidbConnection::serverRetryInterval. Info: In the above configuration, a connection timeout of 10 seconds is specified for every slave. This means if a slave cannot be reached in 10 seconds, it is considered as “dead”. You can adjust this parameter based on your actual environment. You can also configure multiple masters with multiple slaves. For example, [ ’class’ => ’yiidbConnection’, // common configuration for masters ’masterConfig’ => [ ’username’ => ’master’, ’password’ => ’’, ’attributes’ => [ // use a smaller connection timeout PDO::ATTR_TIMEOUT => 10, ], ], // list of master configurations ’masters’ => [ [’dsn’ => ’dsn for master server 1’], [’dsn’ => ’dsn for master server 2’], ], // common configuration for slaves ’slaveConfig’ => [ ’username’ => ’slave’, ’password’ => ’’, ’attributes’ => [ // use a smaller connection timeout PDO::ATTR_TIMEOUT => 10, ], ], // list of slave configurations ’slaves’ => [ [’dsn’ => ’dsn for slave server 1’], [’dsn’ => ’dsn for slave server 2’], [’dsn’ => ’dsn for slave server 3’], [’dsn’ => ’dsn for slave server 4’], ], ] The above configuration specifies two masters and four slaves. The Connection component also supports load balancing and failover about masters, like that about slaves. A difference is that in case none of the masters is available, an exception will be thrown.
  • 222. 216 CHAPTER 6. WORKING WITH DATABASES Note: When you use the yiidbConnection::masters prop- erty to configure one or multiple masters, all other properties for specifying a database connection (e.g. dsn, username, password) with the Connection object itself will be ignored. By default, transactions use the master connection. And within a transac- tion, all DB operations will use the master connection. For example, // the transaction is started on the master connection $transaction = $db->beginTransaction(); try { // both queries are performed against the master $rows = $db->createCommand(’SELECT * FROM user LIMIT 10’)->queryAll(); $db->createCommand("UPDATE user SET username=’demo’ WHERE id=1")-> execute(); $transaction->commit(); } catch(Exception $e) { $transaction->rollBack(); throw $e; } If you want to start a transaction with the slave connection, you should explicitly do so, like the following: $transaction = $db->slave->beginTransaction(); Sometimes, you may want to force using the master connection to perform a read query. This can be achieved with the useMaster() method: $rows = $db->useMaster(function ($db) { return $db->createCommand(’SELECT * FROM user LIMIT 10’)->queryAll(); }); You may also directly set $db->enableSlaves to be false to direct all queries to the master connection. 6.1.7 Working with database schema Getting schema information You can get a yiidbSchema instance like the following: $schema = $connection->getSchema(); It contains a set of methods allowing you to retrieve various information about the database: $tables = $schema->getTableNames(); For the full reference check yiidbSchema.
  • 223. 6.2. QUERY BUILDER AND QUERY 217 Modifying schema Aside from basic SQL queries yiidbCommand contains a set of methods allowing to modify database schema: • createTable, renameTable, dropTable, truncateTable • addColumn, renameColumn, dropColumn, alterColumn • addPrimaryKey, dropPrimaryKey • addForeignKey, dropForeignKey • createIndex, dropIndex These can be used as follows: // CREATE TABLE $connection->createCommand()->createTable(’post’, [ ’id’ => ’pk’, ’title’ => ’string’, ’text’ => ’text’, ]); For the full reference check yiidbCommand. 6.2 Query Builder and Query Note: This section is under development. Yii provides a basic database access layer as described in the Database ba- sics section. The database access layer provides a low-level way to interact with the database. While useful in some situations, it can be tedious and error-prone to write raw SQLs. An alternative approach is to use the Query Builder. The Query Builder provides an object-oriented vehicle for generat- ing queries to be executed. A typical usage of the query builder looks like the following: $rows = (new yiidbQuery()) ->select(’id, name’) ->from(’user’) ->limit(10) ->all(); // which is equivalent to the following code: $query = (new yiidbQuery()) ->select(’id, name’) ->from(’user’) ->limit(10);
  • 224. 218 CHAPTER 6. WORKING WITH DATABASES // Create a command. You can get the actual SQL using $command->sql $command = $query->createCommand(); // Execute the command: $rows = $command->queryAll(); 6.2.1 Query Methods As you can see, yiidbQuery is the main player that you need to deal with. Behind the scene, Query is actually only responsible for representing various query information. The actual query building logic is done by yii dbQueryBuilder when you call the createCommand() method, and the query execution is done by yiidbCommand. For convenience, yiidbQuery provides a set of commonly used query methods that will build the query, execute it, and return the result. For example, • yiidbQuery::all(): builds the query, executes it and returns all results as an array. • yiidbQuery::one(): returns the first row of the result. • yiidbQuery::column(): returns the first column of the result. • yiidbQuery::scalar(): returns the first column in the first row of the result. • yiidbQuery::exists(): returns a value indicating whether the query results in anything. • yiidbQuery::count(): returns the result of a COUNT query. Other similar methods include sum($q), average($q), max($q), min($q), which support the so-called aggregational data query. $q parameter is man- datory for these methods and can be either the column name or ex- pression. 6.2.2 Building Query In the following, we will explain how to build various clauses in a SQL statement. For simplicity, we use $query to represent a yiidbQuery object. SELECT In order to form a basic SELECT query, you need to specify what columns to select and from what table: $query->select(’id, name’) ->from(’user’);
  • 225. 6.2. QUERY BUILDER AND QUERY 219 Select options can be specified as a comma-separated string, as in the above, or as an array. The array syntax is especially useful when forming the selec- tion dynamically: $query->select([’id’, ’name’]) ->from(’user’); Info: You should always use the array format if your SELECT clause contains SQL expressions. This is because a SQL expression like CONCAT(first_name, last_name) AS full_name may contain commas. If you list it together with other columns in a string, the expres- sion may be split into several parts by commas, which is not what you want to see. When specifying columns, you may include the table prefixes or column aliases, e.g., user.id, user.id AS user_id. If you are using array to specify the columns, you may also use the array keys to specify the column aliases, e.g., [’user_id’ => ’user.id’, ’user_name’ => ’user.name’]. To select distinct rows, you may call distinct(), like the following: $query->select(’user_id’)->distinct()->from(’post’); FROM To specify which table(s) to select data from, call from(): $query->select(’*’)->from(’user’); You may specify multiple tables using a comma-separated string or an array. Table names can contain schema prefixes (e.g. ’public.user’) and/or table aliases (e.g. ’user u’). The method will automatically quote the table names unless it contains some parenthesis (which means the table is given as a sub- query or DB expression). For example, $query->select(’u.*, p.*’)->from([’user u’, ’post p’]); When the tables are specified as an array, you may also use the array keys as the table aliases (if a table does not need alias, do not use a string key). For example, $query->select(’u.*, p.*’)->from([’u’ => ’user’, ’p’ => ’post’]); You may specify a sub-query using a Query object. In this case, the corres- ponding array key will be used as the alias for the sub-query. $subQuery = (new Query())->select(’id’)->from(’user’)->where(’status=1’); $query->select(’*’)->from([’u’ => $subQuery]);
  • 226. 220 CHAPTER 6. WORKING WITH DATABASES WHERE Usually data is selected based upon certain criteria. Query Builder has some useful methods to specify these, the most powerful of which being where. It can be used in multiple ways. The simplest way to apply a condition is to use a string: $query->where(’status=:status’, [’:status’ => $status]); When using strings, make sure you’re binding the query parameters, not creating a query by string concatenation. The above approach is safe to use, the following is not: $query->where("status=$status"); // Dangerous! Instead of binding the status value immediately, you can do so using params or addParams: $query->where(’status=:status’); $query->addParams([’:status’ => $status]); Multiple conditions can simultaneously be set in where using the hash format: $query->where([ ’status’ => 10, ’type’ => 2, ’id’ => [4, 8, 15, 16, 23, 42], ]); That code will generate the following SQL: WHERE (‘status‘ = 10) AND (‘type‘ = 2) AND (‘id‘ IN (4, 8, 15, 16, 23, 42)) NULL is a special value in databases, and is handled smartly by the Query Builder. This code: $query->where([’status’ => null]); results in this WHERE clause: WHERE (‘status‘ IS NULL) You can also create sub-queries with Query objects like the following, $userQuery = (new Query)->select(’id’)->from(’user’); $query->where([’id’ => $userQuery]); which will generate the following SQL: WHERE ‘id‘ IN (SELECT ‘id‘ FROM ‘user‘) Another way to use the method is the operand format which is [operator, operand1, operand2, ...]. Operator can be one of the following: • and: the operands should be concatenated together using AND. For example, [’and’, ’id=1’, ’id=2’] will generate id=1 AND id=2. If an op- erand is an array, it will be converted into a string using the rules
  • 227. 6.2. QUERY BUILDER AND QUERY 221 described here. For example, [’and’, ’type=1’, [’or’, ’id=1’, ’id=2’ ]] will generate type=1 AND (id=1 OR id=2). The method will NOT do any quoting or escaping. • or: similar to the and operator except that the operands are concaten- ated using OR. • between: operand 1 should be the column name, and operand 2 and 3 should be the starting and ending values of the range that the column is in. For example, [’between’, ’id’, 1, 10] will generate id BETWEEN 1 AND 10. • not between: similar to between except the BETWEEN is replaced with NOT BETWEEN in the generated condition. • in: operand 1 should be a column or DB expression. Operand 2 can be either an array or a Query object. It will generate an IN condition. If Operand 2 is an array, it will represent the range of the values that the column or DB expression should be; If Operand 2 is a Query object, a sub-query will be generated and used as the range of the column or DB expression. For example, [’in’, ’id’, [1, 2, 3]] will generate id IN (1, 2, 3). The method will properly quote the column name and escape values in the range. The in operator also supports composite columns. In this case, operand 1 should be an array of the columns, while operand 2 should be an array of arrays or a Query object repres- enting the range of the columns. • not in: similar to the in operator except that IN is replaced with NOT IN in the generated condition. • like: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing the values that the column or DB expression should be like. For example, [’like’, ’name’, ’tester’] will generate name LIKE ’%tester%’. When the value range is given as an ar- ray, multiple LIKE predicates will be generated and concatenated using AND. For example, [’like’, ’name’, [’test’, ’sample’]] will generate name LIKE ’%test%’ AND name LIKE ’%sample%’. You may also provide an optional third operand to specify how to escape special characters in the values. The operand should be an array of mappings from the special characters to their escaped counterparts. If this operand is not provided, a default escape mapping will be used. You may use false or an empty array to indicate the values are already escaped and no escape should be applied. Note that when using an escape mapping (or the third operand is not provided), the values will be automatically enclosed within a pair of percentage characters.
  • 228. 222 CHAPTER 6. WORKING WITH DATABASES Note: When using PostgreSQL you may also use ilike12 instead of like for case-insensitive matching. • or like: similar to the like operator except that OR is used to concat- enate the LIKE predicates when operand 2 is an array. • not like: similar to the like operator except that LIKE is replaced with NOT LIKE in the generated condition. • or not like: similar to the not like operator except that OR is used to concatenate the NOT LIKE predicates. • exists: requires one operand which must be an instance of yiidb Query representing the sub-query. It will build a EXISTS (sub-query) expression. • not exists: similar to the exists operator and builds a NOT EXISTS (sub -query) expression. Additionally you can specify anything as operator: $userQuery = (new Query)->select(’id’)->from(’user’); $query->where([’>=’, ’id’, 10]); It will result in: SELECT id FROM user WHERE id >= 10; If you are building parts of condition dynamically it’s very convenient to use andWhere() and orWhere(): $status = 10; $search = ’yii’; $query->where([’status’ => $status]); if (!empty($search)) { $query->andWhere([’like’, ’title’, $search]); } In case $search isn’t empty the following SQL will be generated: WHERE (‘status‘ = 10) AND (‘title‘ LIKE ’%yii%’) Building Filter Conditions When building filter conditions based on user inputs, you usually want to specially handle “empty inputs” by ignoring them in the filters. For example, you have an HTML form that takes user- name and email inputs. If the user only enters something in the username input, you may want to build a query that only tries to match the entered username. You may use the filterWhere() method achieve this goal: 12 http://www.postgresql.org/docs/8.3/static/functions-matching.html# FUNCTIONS-LIKE
  • 229. 6.2. QUERY BUILDER AND QUERY 223 // $username and $email are from user inputs $query->filterWhere([ ’username’ => $username, ’email’ => $email, ]); The filterWhere() method is very similar to where(). The main difference is that filterWhere() will remove empty values from the provided condition. So if $email is “empty”, the resulting query will be ...WHERE username=:username; and if both $username and $email are “empty”, the query will have no WHERE part. A value is empty if it is null, an empty string, a string consisting of whitespaces, or an empty array. You may also use andFilterWhere() and orFilterWhere() to append more filter conditions. ORDER BY For ordering results orderBy and addOrderBy could be used: $query->orderBy([ ’id’ => SORT_ASC, ’name’ => SORT_DESC, ]); Here we are ordering by id ascending and then by name descending. GROUP BY and HAVING In order to add GROUP BY to generated SQL you can use the following: $query->groupBy(’id, status’); If you want to add another field after using groupBy: $query->addGroupBy([’created_at’, ’updated_at’]); To add a HAVING condition the corresponding having method and its andHaving and orHaving can be used. Parameters for these are similar to the ones for where methods group: $query->having([’status’ => $status]); LIMIT and OFFSET To limit result to 10 rows limit can be used: $query->limit(10); To skip 100 fist rows use: $query->offset(100);
  • 230. 224 CHAPTER 6. WORKING WITH DATABASES JOIN The JOIN clauses are generated in the Query Builder by using the applicable join method: • innerJoin() • leftJoin() • rightJoin() This left join selects data from two related tables in one query: $query->select([’user.name AS author’, ’post.title as title’]) ->from(’user’) ->leftJoin(’post’, ’post.user_id = user.id’); In the code, the leftJoin() method’s first parameter specifies the table to join to. The second parameter defines the join condition. If your database application supports other join types, you can use those via the generic join method: $query->join(’FULL OUTER JOIN’, ’post’, ’post.user_id = user.id’); The first argument is the join type to perform. The second is the table to join to, and the third is the condition. Like FROM, you may also join with sub-queries. To do so, specify the sub- query as an array which must contain one element. The array value must be a Query object representing the sub-query, while the array key is the alias for the sub-query. For example, $query->leftJoin([’u’ => $subQuery], ’u.id=author_id’); UNION UNION in SQL adds results of one query to results of another query. Columns returned by both queries should match. In Yii in order to build it you can first form two query objects and then use union method: $query = new Query(); $query->select("id, category_id as type, name")->from(’post’)->limit(10); $anotherQuery = new Query(); $anotherQuery->select(’id, type, name’)->from(’user’)->limit(10); $query->union($anotherQuery);
  • 231. 6.2. QUERY BUILDER AND QUERY 225 6.2.3 Batch Query When working with large amount of data, methods such as yiidbQuery:: all() are not suitable because they require loading all data into the memory. To keep the memory requirement low, Yii provides the so-called batch query support. A batch query makes uses of data cursor and fetches data in batches. Batch query can be used like the following: use yiidbQuery; $query = (new Query()) ->from(’user’) ->orderBy(’id’); foreach ($query->batch() as $users) { // $users is an array of 100 or fewer rows from the user table } // or if you want to iterate the row one by one foreach ($query->each() as $user) { // $user represents one row of data from the user table } The method yiidbQuery::batch() and yiidbQuery::each() return an yiidbBatchQueryResult object which implements the Iterator interface and thus can be used in the foreach construct. During the first iteration, a SQL query is made to the database. Data are since then fetched in batches in the iterations. By default, the batch size is 100, meaning 100 rows of data are being fetched in each batch. You can change the batch size by passing the first parameter to the batch() or each() method. Compared to the yiidbQuery::all(), the batch query only loads 100 rows of data at a time into the memory. If you process the data and then discard it right away, the batch query can help keep the memory usage under a limit. If you specify the query result to be indexed by some column via yiidb Query::indexBy(), the batch query will still keep the proper index. For example, use yiidbQuery; $query = (new Query()) ->from(’user’) ->indexBy(’username’); foreach ($query->batch() as $users) { // $users is indexed by the "username" column } foreach ($query->each() as $username => $user) { }
  • 232. 226 CHAPTER 6. WORKING WITH DATABASES 6.3 Active Record Note: This section is under development. Active Record13 provides an object-oriented interface for accessing data stored in a database. An Active Record class is associated with a database table, an Active Record instance corresponds to a row of that table, and an attribute of an Active Record instance represents the value of a column in that row. Instead of writing raw SQL statements, you can work with Act- ive Record in an object-oriented fashion to manipulate the data in database tables. For example, assume Customer is an Active Record class is associated with the customer table and name is a column of customer table. You can write the following code to insert a new row into customer table: $customer = new Customer(); $customer->name = ’Qiang’; $customer->save(); The above code is equivalent to using the following raw SQL statement, which is less intuitive, more error prone, and may have compatibility prob- lems for different DBMS: $db->createCommand(’INSERT INTO customer (name) VALUES (:name)’, [ ’:name’ => ’Qiang’, ])->execute(); Below is the list of databases that are currently supported by Yii Active Record: • MySQL 4.1 or later: via yiidbActiveRecord • PostgreSQL 7.3 or later: via yiidbActiveRecord • SQLite 2 and 3: via yiidbActiveRecord • Microsoft SQL Server 2010 or later: via yiidbActiveRecord • Oracle: via yiidbActiveRecord • CUBRID 9.1 or later: via yiidbActiveRecord • Sphnix: via yiisphinxActiveRecord, requires yii2-sphinx extension • ElasticSearch: via yiielasticsearchActiveRecord, requires yii2- elasticsearch extension • Redis 2.6.12 or later: via yiiredisActiveRecord, requires yii2- redis extension 13 http://en.wikipedia.org/wiki/Active_record_pattern
  • 233. 6.3. ACTIVE RECORD 227 • MongoDB 1.3.0 or later: via yiimongodbActiveRecord, requires yii2-mongodb extension As you can see, Yii provides Active Record support for relational databases as well as NoSQL databases. In this tutorial, we will mainly describe the usage of Active Record for relational databases. However, most content described here are also applicable to Active Record for NoSQL databases. 6.3.1 Declaring Active Record Classes To declare an Active Record class you need to extend yiidbActiveRecord and implement the tableName method that returns the name of the database table associated with the class: namespace appmodels; use yiidbActiveRecord; class Customer extends ActiveRecord { /** * @return string the name of the table associated with this ActiveRecord class. */ public static function tableName() { return ’customer’; } } 6.3.2 Accessing Column Data Active Record maps each column of the corresponding database table row to an attribute in the Active Record object. An attribute behaves like a regular object public property. The name of an attribute is the same as the corresponding column name and is case-sensitive. To read the value of a column, you can use the following syntax: // "id" and "email" are the names of columns in the table associated with $customer ActiveRecord object $id = $customer->id; $email = $customer->email; To change the value of a column, assign a new value to the associated prop- erty and save the object: $customer->email = ’[email protected]’; $customer->save();
  • 234. 228 CHAPTER 6. WORKING WITH DATABASES 6.3.3 Connecting to Database Active Record uses a yiidbConnection to exchange data with database. By default, it uses the db application component as the connection. As explained in Database basics, you may configure the db component in the application configuration file like follows, return [ ’components’ => [ ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=testdb’, ’username’ => ’demo’, ’password’ => ’demo’, ], ], ]; If you are using multiple databases in your application and you want to use a different DB connection for your Active Record class, you may override the yiidbActiveRecord::getDb() method: class Customer extends ActiveRecord { // ... public static function getDb() { return Yii::$app->db2; // use "db2" application component } } 6.3.4 Querying Data from Database Active Record provides two entry methods for building DB queries and pop- ulating data into Active Record instances: • yiidbActiveRecord::find() • yiidbActiveRecord::findBySql() Both methods return an yiidbActiveQuery instance, which extends yii dbQuery, and thus supports the same set of flexible and powerful DB query building methods, such as where(), join(), orderBy(), etc. The following examples demonstrate some of the possibilities. // to retrieve all *active* customers and order them by their ID: $customers = Customer::find() ->where([’status’ => Customer::STATUS_ACTIVE]) ->orderBy(’id’) ->all();
  • 235. 6.3. ACTIVE RECORD 229 // to return a single customer whose ID is 1: $customer = Customer::find() ->where([’id’ => 1]) ->one(); // to return the number of *active* customers: $count = Customer::find() ->where([’status’ => Customer::STATUS_ACTIVE]) ->count(); // to index the result by customer IDs: $customers = Customer::find()->indexBy(’id’)->all(); // $customers array is indexed by customer IDs // to retrieve customers using a raw SQL statement: $sql = ’SELECT * FROM customer’; $customers = Customer::findBySql($sql)->all(); Tip: In the code above Customer::STATUS_ACTIVE is a constant defined in Customer. It is a good practice to use meaningful con- stant names rather than hardcoded strings or numbers in your code. Two shortcut methods are provided to return Active Record instances match- ing a primary key value or a set of column values: findOne() and findAll(). The former returns the first matching instance while the latter returns all of them. For example, // to return a single customer whose ID is 1: $customer = Customer::findOne(1); // to return an *active* customer whose ID is 1: $customer = Customer::findOne([ ’id’ => 1, ’status’ => Customer::STATUS_ACTIVE, ]); // to return customers whose ID is 1, 2 or 3: $customers = Customer::findAll([1, 2, 3]); // to return customers whose status is "deleted": $customer = Customer::findAll([ ’status’ => Customer::STATUS_DELETED, ]); Retrieving Data in Arrays Sometimes when you are processing a large amount of data, you may want to use arrays to hold the data retrieved from database to save memory. This can be done by calling asArray():
  • 236. 230 CHAPTER 6. WORKING WITH DATABASES // to return customers in terms of arrays rather than ‘Customer‘ objects: $customers = Customer::find() ->asArray() ->all(); // each element of $customers is an array of name-value pairs Note that while this method saves memory and improves performance it is a step to a lower abstraction layer and you will loose some features that the active record layer has. Fetching data using asArray is nearly equal to running a normal query using the query builder. When using asArray the result will be returned just as such a query and no typecasting is performed anymore so the result may contain string values for fields that are integer when accessed on the active record object. Retrieving Data in Batches In Query Builder, we have explained that you may use batch query to keep your memory usage under a limit when querying a large amount of data from database. You may use the same technique in Active Record. For example, // fetch 10 customers at a time foreach (Customer::find()->batch(10) as $customers) { // $customers is an array of 10 or fewer Customer objects } // fetch 10 customers at a time and iterate them one by one foreach (Customer::find()->each(10) as $customer) { // $customer is a Customer object } // batch query with eager loading foreach (Customer::find()->with(’orders’)->each() as $customer) { } 6.3.5 Manipulating Data in Database Active Record provides the following methods to insert, update and delete a single row in a table associated with a single Active Record instance: • yiidbActiveRecord::save() • yiidbActiveRecord::insert() • yiidbActiveRecord::update() • yiidbActiveRecord::delete() Active Record also provides the following static methods that apply to a whole table associated with an Active Record class. Be extremely careful when using these methods as they affect the whole table. For example, deleteAll() will delete ALL rows in the table.
  • 237. 6.3. ACTIVE RECORD 231 • yiidbActiveRecord::updateCounters() • yiidbActiveRecord::updateAll() • yiidbActiveRecord::updateAllCounters() • yiidbActiveRecord::deleteAll() The following examples show how to use these methods: // to insert a new customer record $customer = new Customer(); $customer->name = ’James’; $customer->email = ’[email protected]’; $customer->save(); // equivalent to $customer->insert(); // to update an existing customer record $customer = Customer::findOne($id); $customer->email = ’[email protected]’; $customer->save(); // equivalent to $customer->update(); // to delete an existing customer record $customer = Customer::findOne($id); $customer->delete(); // to delete several customers Customer::deleteAll(’age > :age AND gender = :gender’, [’:age’ => 20, ’: gender’ => ’M’]); // to increment the age of ALL customers by 1 Customer::updateAllCounters([’age’ => 1]); Info: The save() method will call either insert() or update(), depending on whether the Active Record instance is new or not (internally it will check the value of yiidbActiveRecord:: isNewRecord). If an Active Record is instantiated via the new operator, calling save() will insert a row in the table; calling save() on active record fetched from database will update the corresponding row in the table. Data Input and Validation Because Active Record extends from yiibaseModel, it supports the same data input and validation features as described in Model. For example, you may declare validation rules by overwriting the yiibaseModel::rules() method; you may massively assign user input data to an Active Record instance; and you may call yiibaseModel::validate() to trigger data validation.
  • 238. 232 CHAPTER 6. WORKING WITH DATABASES When you call save(), insert() or update(), these methods will automat- ically call yiibaseModel::validate(). If the validation fails, the corres- ponding data saving operation will be cancelled. The following example shows how to use an Active Record to collect/val- idate user input and save them into database: // creating a new record $model = new Customer; if ($model->load(Yii::$app->request->post()) && $model->save()) { // the user input has been collected, validated and saved } // updating a record whose primary key is $id $model = Customer::findOne($id); if ($model === null) { throw new NotFoundHttpException; } if ($model->load(Yii::$app->request->post()) && $model->save()) { // the user input has been collected, validated and saved } Loading Default Values Your table columns may be defined with default values. Sometimes, you may want to pre-populate your Web form for an Active Record with these values. To do so, call the loadDefaultValues() method before rendering the form: $customer = new Customer(); $customer->loadDefaultValues(); // ... render HTML form for $customer ... 6.3.6 Active Record Life Cycles It is important to understand the life cycles of Active Record when it is used to manipulate data in database. These life cycles are typically associated with corresponding events which allow you to inject code to intercept or respond to these events. They are especially useful for developing Active Record behaviors. When instantiating a new Active Record instance, we will have the fol- lowing life cycles: 1. constructor 2. yiidbActiveRecord::init(): will trigger an yiidbActiveRecord ::EVENT_INIT event When querying data through the yiidbActiveRecord::find() method, we will have the following life cycles for EVERY newly populated Active Record instance:
  • 239. 6.3. ACTIVE RECORD 233 1. constructor 2. yiidbActiveRecord::init(): will trigger an yiidbActiveRecord ::EVENT_INIT event 3. yiidbActiveRecord::afterFind(): will trigger an yiidbActiveRecord ::EVENT_AFTER_FIND event When calling yiidbActiveRecord::save() to insert or update an Act- iveRecord, we will have the following life cycles: 1. yiidbActiveRecord::beforeValidate(): will trigger an yiidb ActiveRecord::EVENT_BEFORE_VALIDATE event 2. yiidbActiveRecord::afterValidate(): will trigger an yiidbActiveRecord ::EVENT_AFTER_VALIDATE event 3. yiidbActiveRecord::beforeSave(): will trigger an yiidbActiveRecord ::EVENT_BEFORE_INSERT or yiidbActiveRecord::EVENT_BEFORE_UPDATE event 4. perform the actual data insertion or updating 5. yiidbActiveRecord::afterSave(): will trigger an yiidbActiveRecord ::EVENT_AFTER_INSERT or yiidbActiveRecord::EVENT_AFTER_UPDATE event And Finally when calling yiidbActiveRecord::delete() to delete an ActiveRecord, we will have the following life cycles: 1. yiidbActiveRecord::beforeDelete(): will trigger an yiidbActiveRecord ::EVENT_BEFORE_DELETE event 2. perform the actual data deletion 3. yiidbActiveRecord::afterDelete(): will trigger an yiidbActiveRecord ::EVENT_AFTER_DELETE event 6.3.7 Working with Relational Data You can use ActiveRecord to also query a table’s relational data (i.e., se- lection of data from Table A can also pull in related data from Table B). Thanks to ActiveRecord, the relational data returned can be accessed like a property of the ActiveRecord object associated with the primary table. For example, with an appropriate relation declaration, by accessing $customer ->orders you may obtain an array of Order objects which represent the orders placed by the specified customer.
  • 240. 234 CHAPTER 6. WORKING WITH DATABASES To declare a relation, define a getter method which returns an yiidb ActiveQuery object that has relation information about the relation con- text and thus will only query for related records. For example, class Customer extends yiidbActiveRecord { public function getOrders() { // Customer has_many Order via Order.customer_id -> id return $this->hasMany(Order::className(), [’customer_id’ => ’id’]); } } class Order extends yiidbActiveRecord { public function getCustomer() { // Order has_one Customer via Customer.id -> customer_id return $this->hasOne(Customer::className(), [’id’ => ’customer_id’]) ; } } The methods yiidbActiveRecord::hasMany() and yiidbActiveRecord ::hasOne() used in the above are used to model the many-one relationship and one-one relationship in a relational database. For example, a customer has many orders, and an order has one customer. Both methods take two parameters and return an yiidbActiveQuery object: • $class: the name of the class of the related model(s). This should be a fully qualified class name. • $link: the association between columns from the two tables. This should be given as an array. The keys of the array are the names of the columns from the table associated with $class, while the values of the array are the names of the columns from the declaring class. It is a good practice to define relationships based on table foreign keys. After declaring relations, getting relational data is as easy as accessing a component property that is defined by the corresponding getter method: // get the orders of a customer $customer = Customer::findOne(1); $orders = $customer->orders; // $orders is an array of Order objects Behind the scene, the above code executes the following two SQL queries, one for each line of code: SELECT * FROM customer WHERE id=1; SELECT * FROM order WHERE customer_id=1;
  • 241. 6.3. ACTIVE RECORD 235 Tip: If you access the expression $customer->orders again, it will not perform the second SQL query again. The SQL query is only performed the first time when this expression is accessed. Any further accesses will only return the previously fetched results that are cached internally. If you want to re-query the relational data, simply unset the existing one first: unset($customer->orders );. Sometimes, you may want to pass parameters to a relational query. For example, instead of returning all orders of a customer, you may want to return only big orders whose subtotal exceeds a specified amount. To do so, declare a bigOrders relation with the following getter method: class Customer extends yiidbActiveRecord { public function getBigOrders($threshold = 100) { return $this->hasMany(Order::className(), [’customer_id’ => ’id’]) ->where(’subtotal > :threshold’, [’:threshold’ => $threshold]) ->orderBy(’id’); } } Remember that hasMany() returns an yiidbActiveQuery object which al- lows you to customize the query by calling the methods of yiidbActiveQuery. With the above declaration, if you access $customer->bigOrders, it will only return the orders whose subtotal is greater than 100. To specify a different threshold value, use the following code: $orders = $customer->getBigOrders(200)->all(); Note: A relation method returns an instance of yiidbActiveQuery. If you access the relation like an attribute (i.e. a class prop- erty), the return value will be the query result of the relation, which could be an instance of yiidbActiveRecord, an array of that, or null, depending the multiplicity of the relation. For example, $customer->getOrders() returns an ActiveQuery instance, while $customer->orders returns an array of Order objects (or an empty array if the query results in nothing). 6.3.8 Relations with Pivot Table Sometimes, two tables are related together via an intermediary table called pivot table14. To declare such relations, we can customize the yiidb ActiveQuery object by calling its yiidbActiveQuery::via() or yiidb ActiveQuery::viaTable() method. 14 Pivot table on Wikipedia: http://en.wikipedia.org/wiki/Pivot_table
  • 242. 236 CHAPTER 6. WORKING WITH DATABASES For example, if table order and table item are related via pivot table order_item, we can declare the items relation in the Order class like the fol- lowing: class Order extends yiidbActiveRecord { public function getItems() { return $this->hasMany(Item::className(), [’id’ => ’item_id’]) ->viaTable(’order_item’, [’order_id’ => ’id’]); } } The yiidbActiveQuery::via() method is similar to yiidbActiveQuery ::viaTable() except that the first parameter of yiidbActiveQuery:: via() takes a relation name declared in the ActiveRecord class instead of the pivot table name. For example, the above items relation can be equival- ently declared as follows: class Order extends yiidbActiveRecord { public function getOrderItems() { return $this->hasMany(OrderItem::className(), [’order_id’ => ’id’]); } public function getItems() { return $this->hasMany(Item::className(), [’id’ => ’item_id’]) ->via(’orderItems’); } } 6.3.9 Lazy and Eager Loading As described earlier, when you access the related objects the first time, Act- iveRecord will perform a DB query to retrieve the corresponding data and populate it into the related objects. No query will be performed if you access the same related objects again. We call this lazy loading. For example, // SQL executed: SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); // SQL executed: SELECT * FROM order WHERE customer_id=1 $orders = $customer->orders; // no SQL executed $orders2 = $customer->orders; Lazy loading is very convenient to use. However, it may suffer from a per- formance issue in the following scenario: // SQL executed: SELECT * FROM customer LIMIT 100 $customers = Customer::find()->limit(100)->all();
  • 243. 6.3. ACTIVE RECORD 237 foreach ($customers as $customer) { // SQL executed: SELECT * FROM order WHERE customer_id=... $orders = $customer->orders; // ...handle $orders... } How many SQL queries will be performed in the above code, assuming there are more than 100 customers in the database? 101! The first SQL query brings back 100 customers. Then for each customer, a SQL query is per- formed to bring back the orders of that customer. To solve the above performance problem, you can use the so-called eager loading approach by calling yiidbActiveQuery::with(): // SQL executed: SELECT * FROM customer LIMIT 100; // SELECT * FROM orders WHERE customer_id IN (1,2,...) $customers = Customer::find()->limit(100) ->with(’orders’)->all(); foreach ($customers as $customer) { // no SQL executed $orders = $customer->orders; // ...handle $orders... } As you can see, only two SQL queries are needed for the same task! Info: In general, if you are eager loading N relations among which M relations are defined with via() or viaTable(), a total number of 1+M+N SQL queries will be performed: one query to bring back the rows for the primary table, one for each of the M pivot tables corresponding to the via() or viaTable() calls, and one for each of the N related tables. Note: When you are customizing select() with eager loading, make sure you include the columns that link the related models. Otherwise, the related models will not be loaded. For example, $orders = Order::find()->select([’id’, ’amount’])->with(’customer’)->all(); // $orders[0]->customer is always null. To fix the problem, you should do the following: $orders = Order::find()->select([’id’, ’amount’, ’customer_id’])->with(’ customer’)->all(); Sometimes, you may want to customize the relational queries on the fly. This can be done for both lazy loading and eager loading. For example, $customer = Customer::findOne(1); // lazy loading: SELECT * FROM order WHERE customer_id=1 AND subtotal>100 $orders = $customer->getOrders()->where(’subtotal>100’)->all(); // eager loading: SELECT * FROM customer LIMIT 100
  • 244. 238 CHAPTER 6. WORKING WITH DATABASES // SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100 $customers = Customer::find()->limit(100)->with([ ’orders’ => function($query) { $query->andWhere(’subtotal>100’); }, ])->all(); 6.3.10 Inverse Relations Relations can often be defined in pairs. For example, Customer may have a relation named orders while Order may have a relation named customer: class Customer extends ActiveRecord { .... public function getOrders() { return $this->hasMany(Order::className(), [’customer_id’ => ’id’]); } } class Order extends ActiveRecord { .... public function getCustomer() { return $this->hasOne(Customer::className(), [’id’ => ’customer_id’]) ; } } If we perform the following query, we would find that the customer of an order is not the same customer object that finds those orders, and accessing customer->orders will trigger one SQL execution while accessing the customer of an order will trigger another SQL execution: // SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); // echoes "not equal" // SELECT * FROM order WHERE customer_id=1 // SELECT * FROM customer WHERE id=1 if ($customer->orders[0]->customer === $customer) { echo ’equal’; } else { echo ’not equal’; } To avoid the redundant execution of the last SQL statement, we could declare the inverse relations for the customer and the orders relations by calling the yiidbActiveQuery::inverseOf() method, like the following: class Customer extends ActiveRecord
  • 245. 6.3. ACTIVE RECORD 239 { .... public function getOrders() { return $this->hasMany(Order::className(), [’customer_id’ => ’id’])-> inverseOf(’customer’); } } Now if we execute the same query as shown above, we would get: // SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); // echoes "equal" // SELECT * FROM order WHERE customer_id=1 if ($customer->orders[0]->customer === $customer) { echo ’equal’; } else { echo ’not equal’; } In the above, we have shown how to use inverse relations in lazy loading. Inverse relations also apply in eager loading: // SELECT * FROM customer // SELECT * FROM order WHERE customer_id IN (1, 2, ...) $customers = Customer::find()->with(’orders’)->all(); // echoes "equal" if ($customers[0]->orders[0]->customer === $customers[0]) { echo ’equal’; } else { echo ’not equal’; } Note: Inverse relation cannot be defined with a relation that in- volves pivoting tables. That is, if your relation is defined with yii dbActiveQuery::via() or yiidbActiveQuery::viaTable(), you cannot call yiidbActiveQuery::inverseOf() further. 6.3.11 Joining with Relations When working with relational databases, a common task is to join multiple tables and apply various query conditions and parameters to the JOIN SQL statement. Instead of calling yiidbActiveQuery::join() explicitly to build up the JOIN query, you may reuse the existing relation definitions and call yiidbActiveQuery::joinWith() to achieve this goal. For example, // find all orders and sort the orders by the customer id and the order id. also eager loading "customer" $orders = Order::find()->joinWith(’customer’)->orderBy(’customer.id, order. id’)->all(); // find all orders that contain books, and eager loading "books" $orders = Order::find()->innerJoinWith(’books’)->all();
  • 246. 240 CHAPTER 6. WORKING WITH DATABASES In the above, the method yiidbActiveQuery::innerJoinWith() is a short- cut to yiidbActiveQuery::joinWith() with the join type set as INNER JOIN. You may join with one or multiple relations; you may apply query con- ditions to the relations on-the-fly; and you may also join with sub-relations. For example, // join with multiple relations // find out the orders that contain books and are placed by customers who registered within the past 24 hours $orders = Order::find()->innerJoinWith([ ’books’, ’customer’ => function ($query) { $query->where(’customer.created_at > ’ . (time() - 24 * 3600)); } ])->all(); // join with sub-relations: join with books and books’ authors $orders = Order::find()->joinWith(’books.author’)->all(); Behind the scene, Yii will first execute a JOIN SQL statement to bring back the primary models satisfying the conditions applied to the JOIN SQL. It will then execute a query for each relation and populate the corresponding related records. The difference between yiidbActiveQuery::joinWith() and yiidb ActiveQuery::with() is that the former joins the tables for the primary model class and the related model classes to retrieve the primary models, while the latter just queries against the table for the primary model class to retrieve the primary models. Because of this difference, you may apply query conditions that are only available to a JOIN SQL statement. For example, you may filter the primary models by the conditions on the related models, like the example above. You may also sort the primary models using columns from the related tables. When using yiidbActiveQuery::joinWith(), you are responsible to disambiguate column names. In the above examples, we use item.id and order.id to disambiguate the id column references because both of the order table and the item table contain a column named id. By default, when you join with a relation, the relation will also be eagerly loaded. You may change this behavior by passing the $eagerLoading para- meter which specifies whether to eager load the specified relations. And also by default, yiidbActiveQuery::joinWith() uses LEFT JOIN to join the related tables. You may pass it with the $joinType parameter to customize the join type. As a shortcut to the INNER JOIN type, you may use yiidbActiveQuery::innerJoinWith(). Below are some more examples, // find all orders that contain books, but do not eager loading "books". $orders = Order::find()->innerJoinWith(’books’, false)->all(); // which is equivalent to the above
  • 247. 6.3. ACTIVE RECORD 241 $orders = Order::find()->joinWith(’books’, false, ’INNER JOIN’)->all(); Sometimes when joining two tables, you may need to specify some extra condition in the ON part of the JOIN query. This can be done by calling the yiidbActiveQuery::onCondition() method like the following: class User extends ActiveRecord { public function getBooks() { return $this->hasMany(Item::className(), [’owner_id’ => ’id’])-> onCondition([’category_id’ => 1]); } } In the above, the yiidbActiveRecord::hasMany() method returns an yii dbActiveQuery instance, upon which yiidbActiveQuery::onCondition() is called to specify that only items whose category_id is 1 should be returned. When you perform query using yiidbActiveQuery::joinWith(), the on-condition will be put in the ON part of the corresponding JOIN query. For example, // SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1 // SELECT * FROM item WHERE owner_id IN (...) AND category_id=1 $users = User::find()->joinWith(’books’)->all(); Note that if you use eager loading via yiidbActiveQuery::with() or lazy loading, the on-condition will be put in the WHERE part of the correspond- ing SQL statement, because there is no JOIN query involved. For example, // SELECT * FROM user WHERE id=10 $user = User::findOne(10); // SELECT * FROM item WHERE owner_id=10 AND category_id=1 $books = $user->books; 6.3.12 Working with Relationships ActiveRecord provides the following two methods for establishing and break- ing a relationship between two ActiveRecord objects: • yiidbActiveRecord::link() • yiidbActiveRecord::unlink() For example, given a customer and a new order, we can use the following code to make the order owned by the customer: $customer = Customer::findOne(1); $order = new Order(); $order->subtotal = 100; $customer->link(’orders’, $order);
  • 248. 242 CHAPTER 6. WORKING WITH DATABASES The yiidbActiveRecord::link() call above will set the customer_id of the order to be the primary key value of $customer and then call yiidb ActiveRecord::save() to save the order into database. 6.3.13 Cross-DBMS Relations ActiveRecord allows to establish relationship between entities from differ- ent DBMS. For example: between relational database table and MongoDB collection. Such relation does not require any special code: // Relational database Active Record class Customer extends yiidbActiveRecord { public static function tableName() { return ’customer’; } public function getComments() { // Customer, stored in relational database, has many Comments, stored in MongoDB collection: return $this->hasMany(Comment::className(), [’customer_id’ => ’id’]) ; } } // MongoDb Active Record class Comment extends yiimongodbActiveRecord { public static function collectionName() { return ’comment’; } public function getCustomer() { // Comment, stored in MongoDB collection, has one Customer, stored in relational database: return $this->hasOne(Customer::className(), [’id’ => ’customer_id’]) ; } } All Active Record features like eager and lazy loading, establishing and breaking a relationship and so on, are available for cross-DBMS relations. Note: do not forget Active Record solutions for different DBMS may have specific methods and features, which may not be ap- plied for cross-DBMS relations. For example: usage of yiidb ActiveQuery::joinWith() will obviously not work with rela- tion to the MongoDB collection.
  • 249. 6.3. ACTIVE RECORD 243 6.3.14 Scopes When you call yiidbActiveRecord::find() or yiidbActiveRecord:: findBySql(), it returns an yiidbActiveQuery instance. You may call additional query methods, such as yiidbActiveQuery::where(), yiidb ActiveQuery::orderBy(), to further specify the query conditions. It is possible that you may want to call the same set of query methods in different places. If this is the case, you should consider defining the so-called scopes. A scope is essentially a method defined in a custom query class that calls a set of query methods to modify the query object. You can then use a scope like calling a normal query method. Two steps are required to define a scope. First create a custom query class for your model and define the needed scope methods in this class. For example, create a CommentQuery class for the Comment model and define the active() scope method like the following: namespace appmodels; use yiidbActiveQuery; class CommentQuery extends ActiveQuery { public function active($state = true) { $this->andWhere([’active’ => $state]); return $this; } } Important points are: 1. Class should extend from yiidbActiveQuery (or another ActiveQuery such as yiimongodbActiveQuery). 2. A method should be public and should return $this in order to allow method chaining. It may accept parameters. 3. Check yiidbActiveQuery methods that are very useful for modify- ing query conditions. Second, override yiidbActiveRecord::find() to use the custom query class instead of the regular yiidbActiveQuery. For the example above, you need to write the following code: namespace appmodels; use yiidbActiveRecord; class Comment extends ActiveRecord { /**
  • 250. 244 CHAPTER 6. WORKING WITH DATABASES * @inheritdoc * @return CommentQuery */ public static function find() { return new CommentQuery(get_called_class()); } } That’s it. Now you can use your custom scope methods: $comments = Comment::find()->active()->all(); $inactiveComments = Comment::find()->active(false)->all(); You can also use scopes when defining relations. For example, class Post extends yiidbActiveRecord { public function getActiveComments() { return $this->hasMany(Comment::className(), [’post_id’ => ’id’])-> active(); } } Or use the scopes on-the-fly when performing relational query: $posts = Post::find()->with([ ’comments’ => function($q) { $q->active(); } ])->all(); Default Scope If you used Yii 1.1 before, you may know a concept called default scope. A default scope is a scope that applies to ALL queries. You can define a default scope easily by overriding yiidbActiveRecord::find(). For example, public static function find() { return parent::find()->where([’deleted’ => false]); } Note that all your queries should then not use yiidbActiveQuery::where() but yiidbActiveQuery::andWhere() and yiidbActiveQuery::orWhere() to not override the default condition. 6.3.15 Transactional operations There are two ways of dealing with transactions while working with Active Record. First way is doing everything manually as described in “transac- tions” section of “Database basics“. Another way is to do it by implement-
  • 251. 6.3. ACTIVE RECORD 245 ing transactions method where you can specify which operations are to be wrapped into transaction per model scenario: class Post extends yiidbActiveRecord { public function transactions() { return [ ’admin’ => self::OP_INSERT, ’api’ => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, // the above is equivalent to the following: // ’api’ => self::OP_ALL, ]; } } In the above admin and api are model scenarios and constants starting with OP_ are operations that should be wrapped in transaction for these sceanarios. Supported operations are OP_INSERT, OP_UPDATE and OP_DELETE. OP_ALL stands for all three. Such automatic transactions are especially useful if you’re doing addi- tional database changes in beforeSave, afterSave, beforeDelete, afterDelete and want to be sure that both succeeded before they are saved. 6.3.16 Optimistic Locks Optimistic locking allows multiple users to access the same record for edits and avoids potential conflicts. In case when a user attempts to save the record upon some staled data (because another user has modified the data), a yiidbStaleObjectException exception will be thrown, and the update or deletion is skipped. Optimistic locking is only supported by update() and delete() methods and isn’t used by default. To use Optimistic locking: 1. Create a column to store the version number of each row. The column type should be BIGINT DEFAULT 0. Override optimisticLock() method to return the name of this column. 2. In the Web form that collects the user input, add a hidden field that stores the lock version of the recording being updated. 3. In the controller action that does the data updating, try to catch the yiidbStaleObjectException and implement necessary busi- ness logic (e.g. merging the changes, prompting stated data) to resolve the conflict.
  • 252. 246 CHAPTER 6. WORKING WITH DATABASES 6.3.17 Dirty Attributes An attribute is considered dirty if its value was modified since model was loaded from database or since most recent data save. When saving record data by calling save(), update(), insert() etc. only dirty attributes are saved into database. If there are no dirty attributes there’s nothing to be saved so no query will be issued at all. 6.3.18 See also • Model • yiidbActiveRecord 6.4 Database Migration Note: This section is under development. Like source code, the structure of a database evolves as a database-driven application is developed and maintained. For example, during development, a new table may be added; Or, after the application goes live, it may be discovered that an additional index is required. It is important to keep track of these structural database changes (called migration), just as changes to the source code is tracked using version control. If the source code and the database become out of sync, bugs will occur, or the whole application might break. For this reason, Yii provides a database migration tool that can keep track of database migration history, apply new migrations, or revert existing ones. The following steps show how database migration is used by a team during development: 1. Tim creates a new migration (e.g. creates a new table, changes a column definition, etc.). 2. Tim commits the new migration into the source control system (e.g. Git, Mercurial). 3. Doug updates his repository from the source control system and re- ceives the new migration. 4. Doug applies the migration to his local development database, thereby syncing his database to reflect the changes Tim made. Yii supports database migration via the yii migrate command line tool. This tool supports:
  • 253. 6.4. DATABASE MIGRATION 247 • Creating new migrations • Applying, reverting, and redoing migrations • Showing migration history and new migrations 6.4.1 Creating Migrations To create a new migration, run the following command: yii migrate/create <name> The required name parameter specifies a very brief description of the migra- tion. For example, if the migration creates a new table named news, you’d use the command: yii migrate/create create_news_table As you’ll shortly see, the name parameter is used as part of a PHP class name in the migration. Therefore, it should only contain letters, digits and/or underscore characters. The above command will create a new file named m101129_185401_create_news_table .php. This file will be created within the @app/migrations directory. Initially, the migration file will be generated with the following code: class m101129_185401_create_news_table extends yiidbMigration { public function up() { } public function down() { echo "m101129_185401_create_news_table cannot be reverted.n"; return false; } } Notice that the class name is the same as the file name, and follows the pattern m<timestamp>_<name>, where: • <timestamp> refers to the UTC timestamp (in the format of yymmdd_hhmmss ) when the migration is created, • <name> is taken from the command’s name parameter. In the class, the up() method should contain the code implementing the actual database migration. In other words, the up() method executes code that actually changes the database. The down() method may contain code that reverts the changes made by up(). Sometimes, it is impossible for the down() to undo the database migration. For example, if the migration deletes table rows or an entire table, that data
  • 254. 248 CHAPTER 6. WORKING WITH DATABASES cannot be recovered in the down() method. In such cases, the migration is called irreversible, meaning the database cannot be rolled back to a previous state. When a migration is irreversible, as in the above generated code, the down() method returns false to indicate that the migration cannot be reverted. As an example, let’s show the migration about creating a news table. use yiidbSchema; class m101129_185401_create_news_table extends yiidbMigration { public function up() { $this->createTable(’news’, [ ’id’ => ’pk’, ’title’ => Schema::TYPE_STRING . ’ NOT NULL’, ’content’ => Schema::TYPE_TEXT, ]); } public function down() { $this->dropTable(’news’); } } The base class yiidbMigration exposes a database connection via db property. You can use it for manipulating data and schema of a database. The column types used in this example are abstract types that will be replaced by Yii with the corresponding types depended on your database management system. You can use them to write database independent mi- grations. For example pk will be replaced by int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY for MySQL and integer PRIMARY KEY AUTOINCREMENT NOT NULL for sqlite. See documentation of yiidbQueryBuilder::getColumnType() for more details and a list of available types. You may also use the constants defined in yiidbSchema to define column types. Note: You can add constraints and other custom table options at the end of the table description by specifying them as simple string. For example in the above migration, after content at- tribute definition you can write ’CONSTRAINT ...’ or other custom options. 6.4.2 Transactional Migrations While performing complex DB migrations, we usually want to make sure that each migration succeed or fail as a whole so that the database maintains the
  • 255. 6.4. DATABASE MIGRATION 249 consistency and integrity. In order to achieve this goal, we can exploit DB transactions. We could use special methods safeUp and safeDown for these purposes. use yiidbSchema; class m101129_185401_create_news_table extends yiidbMigration { public function safeUp() { $this->createTable(’news’, [ ’id’ => ’pk’, ’title’ => Schema::TYPE_STRING . ’ NOT NULL’, ’content’ => Schema::TYPE_TEXT, ]); $this->createTable(’user’, [ ’id’ => ’pk’, ’login’ => Schema::TYPE_STRING . ’ NOT NULL’, ’password’ => Schema::TYPE_STRING . ’ NOT NULL’, ]); } public function safeDown() { $this->dropTable(’news’); $this->dropTable(’user’); } } When your code uses more then one query it is recommended to use safeUp and safeDown. Note: Not all DBMS support transactions. And some DB queries cannot be put into a transaction. In this case, you will have to implement up() and down(), instead. And for MySQL, some SQL statements may cause implicit commit15. 6.4.3 Applying Migrations To apply all available new migrations (i.e., make the local database up-to- date), run the following command: yii migrate The command will show the list of all new migrations. If you confirm to apply the migrations, it will run the up() method in every new migration class, one after another, in the order of the timestamp value in the class name. 15 http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html
  • 256. 250 CHAPTER 6. WORKING WITH DATABASES After applying a migration, the migration tool will keep a record in a database table named migration. This allows the tool to identify which mi- grations have been applied and which are not. If the migration table does not exist, the tool will automatically create it in the database specified by the db application component. Sometimes, we may only want to apply one or a few new migrations. We can use the following command: yii migrate/up 3 This command will apply the 3 new migrations. Changing the value 3 will allow us to change the number of migrations to be applied. We can also migrate the database to a specific version with the following command: yii migrate/to 101129_185401 That is, we use the timestamp part of a migration name to specify the version that we want to migrate the database to. If there are multiple migrations between the last applied migration and the specified migration, all these migrations will be applied. If the specified migration has been applied before, then all migrations applied after it will be reverted (to be described in the next section). 6.4.4 Reverting Migrations To revert the last one or several applied migrations, we can use the following command: yii migrate/down [step] where the optional step parameter specifies how many migrations to be rever- ted back. It defaults to 1, meaning reverting back the last applied migration. As we described before, not all migrations can be reverted. Trying to revert such migrations will throw an exception and stop the whole reverting process. 6.4.5 Redoing Migrations Redoing migrations means first reverting and then applying the specified migrations. This can be done with the following command: yii migrate/redo [step] where the optional step parameter specifies how many migrations to be re- done. It defaults to 1, meaning redoing the last migration.
  • 257. 6.4. DATABASE MIGRATION 251 6.4.6 Showing Migration Information Besides applying and reverting migrations, the migration tool can also dis- play the migration history and the new migrations to be applied. yii migrate/history [limit] yii migrate/new [limit] where the optional parameter limit specifies the number of migrations to be displayed. If limit is not specified, all available migrations will be displayed. The first command shows the migrations that have been applied, while the second command shows the migrations that have not been applied. 6.4.7 Modifying Migration History Sometimes, we may want to modify the migration history to a specific migra- tion version without actually applying or reverting the relevant migrations. This often happens when developing a new migration. We can use the fol- lowing command to achieve this goal. yii migrate/mark 101129_185401 This command is very similar to yii migrate/to command, except that it only modifies the migration history table to the specified version without applying or reverting the migrations. 6.4.8 Customizing Migration Command There are several ways to customize the migration command. Use Command Line Options The migration command comes with four options that can be specified in command line: • interactive: boolean, specifies whether to perform migrations in an interactive mode. Defaults to true, meaning the user will be prompted when performing a specific migration. You may set this to false should the migrations be done in a background process. • migrationPath: string, specifies the directory storing all migration class files. This must be specified in terms of a path alias, and the corres- ponding directory must exist. If not specified, it will use the migrations sub-directory under the application base path. • migrationTable: string, specifies the name of the database table for storing migration history information. It defaults to migration. The table structure is version varchar(255) primary key, apply_time integer.
  • 258. 252 CHAPTER 6. WORKING WITH DATABASES • db: string, specifies the ID of the database application component. Defaults to ‘db’. • templateFile: string, specifies the path of the file to be served as the code template for generating the migration classes. This must be spe- cified in terms of a path alias (e.g. application.migrations.template). If not set, an internal template will be used. Inside the template, the token {ClassName} will be replaced with the actual migration class name. To specify these options, execute the migrate command using the following format yii migrate/up --option1=value1 --option2=value2 ... For example, if we want to migrate for a forum module whose migration files are located within the module’s migrations directory, we can use the following command: yii migrate/up --migrationPath=@app/modules/forum/migrations Configure Command Globally While command line options allow us to configure the migration command on-the-fly, sometimes we may want to configure the command once for all. For example, we may want to use a different table to store the migration history, or we may want to use a customized migration template. We can do so by modifying the console application’s configuration file like the following, ’controllerMap’ => [ ’migrate’ => [ ’class’ => ’yiiconsolecontrollersMigrateController’, ’migrationTable’ => ’my_custom_migrate_table’, ], ] Now if we run the migrate command, the above configurations will take effect without requiring us to enter the command line options every time. Other command options can be also configured this way. Migrating with Multiple Databases By default, migrations will be applied to the database specified by the db application component. You may change it by specifying the --db option, for example, yii migrate --db=db2 The above command will apply all migrations found in the default migration path to the db2 database. If your application works with multiple databases, it is possible that some migrations should be applied to one database while some others should be
  • 259. 6.4. DATABASE MIGRATION 253 applied to another database. In this case, it is recommended that you create a base migration class for each different database and override the yiidb Migration::init() method like the following, public function init() { $this->db = ’db2’; parent::init(); } To create a migration that should be applied to a particular database, simply extend from the corresponding base migration class. Now if you run the yii migrate command, each migration will be applied to its corresponding database. Info: Because each migration uses hardcoded DB connection, the --db option of the migrate command will have no effect. Also note that the migration history will be stored in the default db database. If you want to support changing DB connection via the --db option, you may take the following alternative approach to work with multiple databases. For each database, create a migration path and save all corresponding migration classes there. To apply migrations, run the command as follows, yii migrate --migrationPath=@app/migrations/db1 --db=db1 yii migrate --migrationPath=@app/migrations/db2 --db=db2 ... Info: The above approach stores the migration history in different databases specified via the --db option.
  • 260. 254 CHAPTER 6. WORKING WITH DATABASES Error: not existing file: db-sphinx.md
  • 261. 6.4. DATABASE MIGRATION 255 Error: not existing file: db-redis.md
  • 262. 256 CHAPTER 6. WORKING WITH DATABASES Error: not existing file: db-mongodb.md
  • 263. 6.4. DATABASE MIGRATION 257 Error: not existing file: db-elasticsearch.md
  • 264. 258 CHAPTER 6. WORKING WITH DATABASES
  • 265. Chapter 7 Getting Data from Users 7.1 Working with Forms Note: This section is under development. The primary way of using forms in Yii is through yiiwidgetsActiveForm. This approach should be preferred when the form is based upon a model. Additionally, there are some useful methods in yiihelpersHtml that are typically used for adding buttons and help text to any form. When creating model-based forms, the first step is to define the model itself. The model can be either based upon the Active Record class, or the more generic Model class. For this login example, a generic model will be used: use yiibaseModel; class LoginForm extends Model { public $username; public $password; /** * @return array the validation rules. */ public function rules() { return [ // username and password are both required [[’username’, ’password’], ’required’], // password is validated by validatePassword() [’password’, ’validatePassword’], ]; } /** * Validates the password. * This method serves as the inline validation for password. 259
  • 266. 260 CHAPTER 7. GETTING DATA FROM USERS */ public function validatePassword() { $user = User::findByUsername($this->username); if (!$user || !$user->validatePassword($this->password)) { $this->addError(’password’, ’Incorrect username or password.’); } } /** * Logs in a user using the provided username and password. * @return boolean whether the user is logged in successfully */ public function login() { if ($this->validate()) { $user = User::findByUsername($this->username); return true; } else { return false; } } } The controller will pass an instance of that model to the view, wherein the yiiwidgetsActiveForm widget is used: use yiihelpersHtml; use yiiwidgetsActiveForm; <?php $form = ActiveForm::begin([ ’id’ => ’login-form’, ’options’ => [’class’ => ’form-horizontal’], ]) ?> <?= $form->field($model, ’username’) ?> <?= $form->field($model, ’password’)->passwordInput() ?> <div class="form-group"> <div class="col-lg-offset-1 col-lg-11"> <?= Html::submitButton(’Login’, [’class’ => ’btn btn-primary’]) ?> </div> </div> <?php ActiveForm::end() ?> In the above code, yiiwidgetsActiveForm::begin() not only creates a form instance, but also marks the beginning of the form. All of the con- tent placed between yiiwidgetsActiveForm::begin() and yiiwidgets ActiveForm::end() will be wrapped within the <form> tag. As with any widget, you can specify some options as to how the widget should be con- figured by passing an array to the begin method. In this case, an extra CSS class and identifying ID are passed to be used in the opening <form> tag.
  • 267. 7.1. WORKING WITH FORMS 261 In order to create a form element in the form, along with the element’s la- bel, and any application JavaScript validation, the yiiwidgetsActiveForm ::field() method of the Active Form widget is called. When the invoca- tion of this method is echoed directly, the result is a regular (text) input. To customize the output, you can chain additional methods to this call: <?= $form->field($model, ’password’)->passwordInput() ?> // or <?= $form->field($model, ’username’)->textInput()->hint(’Please enter your name’)->label(’Name’) ?> This will create all the <label>, <input> and other tags according to the template defined by the form field. To add these tags yourself you can use the Html helper class. If you want to use one of HTML5 fields you may specify input type directly like the following: <?= $form->field($model, ’email’)->input(’email’) ?> Specifying the attribute of the model can be done in more sophisticated ways. For example when an attribute may take an array value when uploading multiple files or selecting multiple items you may specify it by appending [] to the attribute name: // allow multiple files to be uploaded: echo $form->field($model, ’uploadFile[]’)->fileInput([’multiple’=>’multiple’ ]); // allow multiple items to be checked: echo $form->field($model, ’items[]’)->checkboxList([’a’ => ’Item A’, ’b’ => ’Item B’, ’c’ => ’Item C’]); Tip: in order to style required fields with asterisk you can use the following CSS: div.required label:after { content: " *"; color: red; } 7.1.1 Handling multiple models with a single form Sometimes you need to handle multiple models of the same kind in a single form. For example, multiple settings where each setting is stored as name- value and is represented by Setting model. The following shows how to implement it with Yii. Let’s start with controller action:
  • 268. 262 CHAPTER 7. GETTING DATA FROM USERS namespace appcontrollers; use Yii; use yiibaseModel; use yiiwebController; use appmodelsSetting; class SettingsController extends Controller { // ... public function actionUpdate() { $settings = Setting::find()->indexBy(’id’)->all(); if (Model::loadMultiple($settings, Yii::$app->request->post()) && Model::validateMultiple($settings)) { foreach ($settings as $setting) { $setting->save(false); } return $this->redirect(’index’); } return $this->render(’update’, [’settings’ => $settings]); } } In the code above we’re using indexBy when retrieving models from database to make array indexed by model ids. These will be later used to identify form fields. loadMultiple fills multiple modelds with the form data coming from POST and validateMultiple validates all models at once. In order to skip validation when saving we’re passing false as a parameter to save. Now the form that’s in update view: <?php use yiihelpersHtml; use yiiwidgetsActiveForm; $form = ActiveForm::begin(); foreach ($settings as $index => $setting) { echo Html::encode($setting->name) . ’: ’ . $form->field($setting, "[ $index]value"); } ActiveForm::end(); Here for each setting we are rendering name and an input with a value. It is important to add a proper index to input name since that is how loadMultiple determines which model to fill with which values.
  • 269. 7.2. VALIDATING INPUT 263 7.2 Validating Input As a rule of thumb, you should never trust the data received from end users and should always validate them before putting them to good use. Given a model populated with user inputs, you can validate the inputs by calling the yiibaseModel::validate() method. The method will return a boolean value indicating whether the validation succeeds or not. If not, you may get the error messages from the yiibaseModel::errors property. For example, $model = new appmodelsContactForm; // populate model attributes with user inputs $model->attributes = Yii::$app->request->post(’ContactForm’); if ($model->validate()) { // all inputs are valid } else { // validation failed: $errors is an array containing error messages $errors = $model->errors; } Behind the scene, the validate() method does the following steps to perform validation: 1. Determine which attributes should be validated by getting the attrib- ute list from yiibaseModel::scenarios() using the current yii baseModel::scenario. These attributes are called active attributes. 2. Determine which validation rules should be used by getting the rule list from yiibaseModel::rules() using the current yiibaseModel:: scenario. These rules are called active rules. 3. Use each active rule to validate each active attribute associated with that rule. If the rule fails, keep an error message for the attribute in the model. 7.2.1 Declaring Rules To make validate() really work, you should declare validation rules for the attributes you plan to validate. This should be done by overriding the yii baseModel::rules() method. The following example shows how the val- idation rules for the ContactForm model are declared: public function rules() { return [ // the name, email, subject and body attributes are required [[’name’, ’email’, ’subject’, ’body’], ’required’],
  • 270. 264 CHAPTER 7. GETTING DATA FROM USERS // the email attribute should be a valid email address [’email’, ’email’], ]; } The yiibaseModel::rules() method should return an array of rules, each of which is an array of the following format: [ // required, specifies which attributes should be validated by this rule . // For a single attribute, you can use the attribute name directly // without having it in an array instead of an array [’attribute1’, ’attribute2’, ...], // required, specifies the type of this rule. // It can be a class name, validator alias, or a validation method name ’validator’, // optional, specifies in which scenario(s) this rule should be applied // if not given, it means the rule applies to all scenarios // You may also configure the "except" option if you want to apply the rule // to all scenarios except the listed ones ’on’ => [’scenario1’, ’scenario2’, ...], // optional, specifies additional configurations for the validator object ’property1’ => ’value1’, ’property2’ => ’value2’, ... ] For each rule you must specify at least which attributes the rule applies to and what is the type of the rule. You can specify the rule type in one of the following forms: • the alias of a core validator, such as required, in, date, etc. Please refer to the Core Validators for the complete list of core validators. • the name of a validation method in the model class, or an anonymous function. Please refer to the Inline Validators subsection for more details. • the name of a validator class. Please refer to the Standalone Validators subsection for more details. A rule can be used to validate one or multiple attributes, and an attribute may be validated by one or multiple rules. A rule may be applied in certain scenarios only by specifying the on option. If you do not specify an on option, it means the rule will be applied to all scenarios. When the validate() method is called, it does the following steps to perform validation:
  • 271. 7.2. VALIDATING INPUT 265 1. Determine which attributes should be validated by checking the cur- rent yiibaseModel::scenario against the scenarios declared in yii baseModel::scenarios(). These attributes are the active attrib- utes. 2. Determine which rules should be applied by checking the current yii baseModel::scenario against the rules declared in yiibaseModel ::rules(). These rules are the active rules. 3. Use each active rule to validate each active attribute which is associated with the rule. The validation rules are evaluated in the order they are listed. According to the above validation steps, an attribute will be validated if and only if it is an active attribute declared in scenarios() and is associated with one or multiple active rules declared in rules(). Customizing Error Messages Most validators have default error messages that will be added to the model being validated when its attributes fail the validation. For example, the yii validatorsRequiredValidator validator will add a message “Username cannot be blank.” to a model when the username attribute fails the rule using this validator. You can customize the error message of a rule by specifying the message property when declaring the rule, like the following, public function rules() { return [ [’username’, ’required’, ’message’ => ’Please choose a username.’], ]; } Some validators may support additional error messages to more precisely de- scribe different causes of validation failures. For example, the yiivalidators NumberValidator validator supports yiivalidatorsNumberValidator ::tooBig and yiivalidatorsNumberValidator::tooSmall to describe the validation failure when the value being validated is too big and too small, respectively. You may configure these error messages like configuring other properties of validators in a validation rule. Validation Events When yiibaseModel::validate() is called, it will call two methods that you may override to customize the validation process:
  • 272. 266 CHAPTER 7. GETTING DATA FROM USERS • yiibaseModel::beforeValidate(): the default implementation will trigger a yiibaseModel::EVENT_BEFORE_VALIDATE event. You may either override this method or respond to this event to do some pre- processing work (e.g. normalizing data inputs) before the validation occurs. The method should return a boolean value indicating whether the validation should proceed or not. • yiibaseModel::afterValidate(): the default implementation will trigger a yiibaseModel::EVENT_AFTER_VALIDATE event. You may either override this method or respond to this event to do some post- processing work after the validation is completed. Conditional Validation To validate attributes only when certain conditions apply, e.g. the validation of one attribute depends on the value of another attribute you can use the yiivalidatorsValidator::when property to define such conditions. For example, [ [’state’, ’required’, ’when’ => function($model) { return $model->country == ’USA’; }], ] The yiivalidatorsValidator::when property takes a PHP callable with the following signature: /** * @param Model $model the model being validated * @param string $attribute the attribute being validated * @return boolean whether the rule should be applied */ function ($model, $attribute) If you also need to support client-side conditional validation, you should con- figure the yiivalidatorsValidator::whenClient property which takes a string representing a JavaScript function whose return value determines whether to apply the rule or not. For example, [ [’state’, ’required’, ’when’ => function ($model) { return $model->country == ’USA’; }, ’whenClient’ => "function (attribute, value) { return $(’#country’).val() == ’USA’; }"], ]
  • 273. 7.2. VALIDATING INPUT 267 Data Filtering User inputs often need to be filtered or preprocessed. For example, you may want to trim the spaces around the username input. You may use validation rules to achieve this goal. The following examples shows how to trim the spaces in the inputs and turn empty inputs into nulls by using the trim and default core validators: [ [[’username’, ’email’], ’trim’], [[’username’, ’email’], ’default’], ] You may also use the more general filter validator to perform more complex data filtering. As you can see, these validation rules do not really validate the inputs. Instead, they will process the values and save them back to the attributes being validated. Handling Empty Inputs When input data are submitted from HTML forms, you often need to assign some default values to the inputs if they are empty. You can do so by using the default validator. For example, [ // set "username" and "email" as null if they are empty [[’username’, ’email’], ’default’], // set "level" to be 1 if it is empty [’level’, ’default’, ’value’ => 1], ] By default, an input is considered empty if its value is an empty string, an empty array or a null. You may customize the default empty detection logic by configuring the the yiivalidatorsValidator::isEmpty property with a PHP callable. For example, [ [’agree’, ’required’, ’isEmpty’ => function ($value) { return empty($value); }], ] Note: Most validators do not handle empty inputs if their yii baseValidator::skipOnEmpty property takes the default value true. They will simply be skipped during validation if their asso- ciated attributes receive empty inputs. Among the core validat- ors, only the captcha, default, filter, required, and trim validators will handle empty inputs.
  • 274. 268 CHAPTER 7. GETTING DATA FROM USERS 7.2.2 Ad Hoc Validation Sometimes you need to do ad hoc validation for values that are not bound to any model. If you only need to perform one type of validation (e.g. validating email addresses), you may call the yiivalidatorsValidator::validate() method of the desired validator, like the following: $email = ’[email protected]’; $validator = new yiivalidatorsEmailValidator(); if ($validator->validate($email, $error)) { echo ’Email is valid.’; } else { echo $error; } Note: Not all validators support such kind of validation. An example is the unique core validator which is designed to work with a model only. If you need to perform multiple validations against several values, you can use yiibaseDynamicModel which supports declaring both attributes and rules on the fly. Its usage is like the following: public function actionSearch($name, $email) { $model = DynamicModel::validateData(compact(’name’, ’email’), [ [[’name’, ’email’], ’string’, ’max’ => 128], [’email’, ’email’], ]); if ($model->hasErrors()) { // validation fails } else { // validation succeeds } } The yiibaseDynamicModel::validateData() method creates an instance of DynamicModel, defines the attributes using the given data (name and email in this example), and then calls yiibaseModel::validate() with the given rules. Alternatively, you may use the following more “classic” syntax to perform ad hoc data validation: public function actionSearch($name, $email) { $model = new DynamicModel(compact(’name’, ’email’)); $model->addRule([’name’, ’email’], ’string’, [’max’ => 128]) ->addRule(’email’, ’email’) ->validate();
  • 275. 7.2. VALIDATING INPUT 269 if ($model->hasErrors()) { // validation fails } else { // validation succeeds } } After validation, you can check if the validation succeeds or not by calling the yiibaseDynamicModel::hasErrors() method, and then get the val- idation errors from the yiibaseDynamicModel::errors property, like you do with a normal model. You may also access the dynamic attributes defined through the model instance, e.g., $model->name and $model->email. 7.2.3 Creating Validators Besides using the core validators included in the Yii releases, you may also create your own validators. You may create inline validators or standalone validators. Inline Validators An inline validator is one defined in terms of a model method or an anonym- ous function. The signature of the method/function is: /** * @param string $attribute the attribute currently being validated * @param array $params the additional name-value pairs given in the rule */ function ($attribute, $params) If an attribute fails the validation, the method/function should call yiibase Model::addError() to save the error message in the model so that it can be retrieved back later to present to end users. Below are some examples: use yiibaseModel; class MyForm extends Model { public $country; public $token; public function rules() { return [ // an inline validator defined as the model method validateCountry() [’country’, ’validateCountry’], // an inline validator defined as an anonymous function [’token’, function ($attribute, $params) {
  • 276. 270 CHAPTER 7. GETTING DATA FROM USERS if (!ctype_alnum($this->$attribute)) { $this->addError($attribute, ’The token must contain letters or digits.’); } }], ]; } public function validateCountry($attribute, $params) { if (!in_array($this->$attribute, [’USA’, ’Web’])) { $this->addError($attribute, ’The country must be either "USA" or "Web".’); } } } Note: By default, inline validators will not be applied if their as- sociated attributes receive empty inputs or if they have already failed some validation rules. If you want to make sure a rule is al- ways applied, you may configure the yiivalidatorsValidator ::skipOnEmpty and/or yiivalidatorsValidator::skipOnError properties to be false in the rule declarations. For example: ‘php [ [’country’, ’validateCountry’, ’skipOnEmpty’ => false, ’ skipOnError’ => false], ] ‘ Standalone Validators A standalone validator is a class extending yiivalidatorsValidator or its child class. You may implement its validation logic by overriding the yiivalidatorsValidator::validateAttribute() method. If an attrib- ute fails the validation, call yiibaseModel::addError() to save the error message in the model, like you do with inline validators. For example, namespace appcomponents; use yiivalidatorsValidator; class CountryValidator extends Validator { public function validateAttribute($model, $attribute) { if (!in_array($model->$attribute, [’USA’, ’Web’])) { $this->addError($attribute, ’The country must be either "USA" or "Web".’); } } }
  • 277. 7.2. VALIDATING INPUT 271 If you want your validator to support validating a value without a model, you should also override yiivalidatorsValidator::validate(). You may also override yiivalidatorsValidator::validateValue() instead of validateAttribute() and validate() because by default the latter two meth- ods are implemented by calling validateValue(). 7.2.4 Client-Side Validation Client-side validation based on JavaScript is desirable when end users provide inputs via HTML forms, because it allows users to find out input errors faster and thus provides better user experience. You may use or implement a valid- ator that supports client-side validation in addition to server-side validation. Info: While client-side validation is desirable, it is not a must. Its main purpose is to provide users better experience. Like input data coming from end users, you should never trust client-side validation. For this reason, you should always perform server- side validation by calling yiibaseModel::validate(), like de- scribed in the previous subsections. Using Client-Side Validation Many core validators support client-side validation out-of-box. All you need to do is just to use yiiwidgetsActiveForm to build your HTML forms. For example, LoginForm below declares two rules: one uses the required core validator which is supported on both client and server sides; the other uses the validatePassword inline validator which is only supported on the server side. namespace appmodels; use yiibaseModel; use appmodelsUser; class LoginForm extends Model { public $username; public $password; public function rules() { return [ // username and password are both required [[’username’, ’password’], ’required’], // password is validated by validatePassword() [’password’, ’validatePassword’], ]; }
  • 278. 272 CHAPTER 7. GETTING DATA FROM USERS public function validatePassword() { $user = User::findByUsername($this->username); if (!$user || !$user->validatePassword($this->password)) { $this->addError(’password’, ’Incorrect username or password.’); } } } The HTML form built by the following code contains two input fields username and password. If you submit the form without entering anything, you will find the error messages requiring you to enter something appear right away without any communication with the server. <?php $form = yiiwidgetsActiveForm::begin(); ?> <?= $form->field($model, ’username’) ?> <?= $form->field($model, ’password’)->passwordInput() ?> <?= Html::submitButton(’Login’) ?> <?php yiiwidgetsActiveForm::end(); ?> Behind the scene, yiiwidgetsActiveForm will read the validation rules declared in the model and generate appropriate JavaScript code for validators that support client-side validation. When a user changes the value of an input field or submit the form, the client-side validation JavaScript will be triggered. If you want to turn off client-side validation completely, you may config- ure the yiiwidgetsActiveForm::enableClientValidation property to be false. You may also turn off client-side validation of individual input fields by configuring their yiiwidgetsActiveField::enableClientValidation property to be false. Implementing Client-Side Validation To create a validator that supports client-side validation, you should imple- ment the yiivalidatorsValidator::clientValidateAttribute() method which returns a piece of JavaScript code that performs the validation on the client side. Within the JavaScript code, you may use the following predefined variables: • attribute: the name of the attribute being validated. • value: the value being validated. • messages: an array used to hold the validation error messages for the attribute. • deferred: an array which deferred objects can be pushed into (explained in the next subsection).
  • 279. 7.2. VALIDATING INPUT 273 In the following example, we create a StatusValidator which validates if an input is a valid status input against the existing status data. The validator supports both server side and client side validation. namespace appcomponents; use yiivalidatorsValidator; use appmodelsStatus; class StatusValidator extends Validator { public function init() { parent::init(); $this->message = ’Invalid status input.’; } public function validateAttribute($model, $attribute) { $value = $model->$attribute; if (!Status::find()->where([’id’ => $value])->exists()) { $model->addError($attribute, $this->message); } } public function clientValidateAttribute($model, $attribute, $view) { $statuses = json_encode(Status::find()->select(’id’)->asArray()-> column()); $message = json_encode($this->message); return <<<JS if (!$.inArray(value, $statuses)) { messages.push($message); } JS; } } Tip: The above code is given mainly to demonstrate how to sup- port client-side validation. In practice, you may use the in core validator to achieve the same goal. You may write the validation rule like the following: ‘php [ [’status’, ’in’, ’range’ => Status::find()->select(’id’)-> asArray()->column()], ] ‘
  • 280. 274 CHAPTER 7. GETTING DATA FROM USERS Deferred Validation If you need to perform asynchronous client-side validation, you can create Deferred objects1. For example, to perform a custom AJAX validation, you can use the following code: public function clientValidateAttribute($model, $attribute, $view) { return <<<JS deferred.push($.get("/check", {value: value}).done(function(data) { if (’’ !== data) { messages.push(data); } })); JS; } In the above, the deferred variable is provided by Yii, which is an array of Deferred objects. The $.get() jQuery method creates a Deferred object which is pushed to the deferred array. You can also explicitly create a Deferred object and call its resolve() method when the asynchronous callback is hit. The following example shows how to validate the dimensions of an uploaded image file on the client side. public function clientValidateAttribute($model, $attribute, $view) { return <<<JS var def = $.Deferred(); var img = new Image(); img.onload = function() { if (this.width > 150) { messages.push(’Image too wide!!’); } def.resolve(); } var reader = new FileReader(); reader.onloadend = function() { img.src = reader.result; } reader.readAsDataURL(file); deferred.push(def); JS; } Note: The resolve() method must be called after the attribute has been validated. Otherwise the main form validation will not complete. For simplicity, the deferred array is equipped with a shortcut method add() which automatically creates a Deferred object and add it to the deferred 1 http://api.jquery.com/category/deferred-object/
  • 281. 7.2. VALIDATING INPUT 275 array. Using this method, you can simplify the above example as follows, public function clientValidateAttribute($model, $attribute, $view) { return <<<JS deferred.add(function(def) { var img = new Image(); img.onload = function() { if (this.width > 150) { messages.push(’Image too wide!!’); } def.resolve(); } var reader = new FileReader(); reader.onloadend = function() { img.src = reader.result; } reader.readAsDataURL(file); }); JS; } AJAX Validation Some validations can only be done on the server side, because only the server has the necessary information. For example, to validate if a username is unique or not, it is necessary to check the user table on the server side. You can use AJAX-based validation in this case. It will trigger an AJAX request in the background to validate the input while keeping the same user experience as the regular client-side validation. To enable AJAX validation for the whole form, you have to set the yiiwidgetsActiveForm::enableAjaxValidation property to be true and specify id to be unique form identifier: <?php $form = yiiwidgetsActiveForm::begin([ ’id’ => ’contact-form’, ’enableAjaxValidation’ => true, ]); ?> You may also turn AJAX validation on or off for individual input fields by configuring their yiiwidgetsActiveField::enableAjaxValidation prop- erty. You also need to prepare the server so that it can handle the AJAX validation requests. This can be achieved by a code snippet like the following in controller actions: if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { Yii::$app->response->format = Response::FORMAT_JSON; return ActiveForm::validate($model); }
  • 282. 276 CHAPTER 7. GETTING DATA FROM USERS The above code will check whether the current request is an AJAX. If yes, it will respond to this request by running the validation and returning the errors in JSON format. Info: You can also use Deferred Validation to perform AJAX validation. However, the AJAX validation feature described here is more systematic and requires less coding effort. 7.3 Uploading Files Note: This section is under development. Uploading files in Yii is done via form model, its validation rules and some controller code. Let’s review what’s needed to handle uploads properly. 7.3.1 Form model First of all, you need to create a model that will handle file upload. Create models/UploadForm.php with the following content: namespace appmodels; use yiibaseModel; use yiiwebUploadedFile; /** * UploadForm is the model behind the upload form. */ class UploadForm extends Model { /** * @var UploadedFile|Null file attribute */ public $file; /** * @return array the validation rules. */ public function rules() { return [ [[’file’], ’file’], ]; } } In the code above, we created a model UploadForm with an attribute $file that will become <input type="file"> in the HTML form. The attribute has the validation rule named file that uses yiivalidatorsFileValidator.
  • 283. 7.3. UPLOADING FILES 277 7.3.2 Form view Next create a view that will render the form. <?php use yiiwidgetsActiveForm; $form = ActiveForm::begin([’options’ => [’enctype’ => ’multipart/form-data’ ]]); ?> <?= $form->field($model, ’file’)->fileInput() ?> <button>Submit</button> <?php ActiveForm::end(); ?> The ’enctype’ => ’multipart/form-data’ is important since it allows file up- loads. fileInput() represents a form input field. 7.3.3 Controller Now create the controller that connects form and model together: namespace appcontrollers; use Yii; use yiiwebController; use appmodelsUploadForm; use yiiwebUploadedFile; class SiteController extends Controller { public function actionUpload() { $model = new UploadForm(); if (Yii::$app->request->isPost) { $model->file = UploadedFile::getInstance($model, ’file’); if ($model->validate()) { $model->file->saveAs(’uploads/’ . $model->file->baseName . ’ .’ . $model->file->extension); } } return $this->render(’upload’, [’model’ => $model]); } } Instead of model->load(...) we are using UploadedFile::getInstance(...). yii webUploadedFile does not run the model validation. It only provides information about the uploaded file. Therefore, you need to run valid- ation manually via $model->validate(). This triggers the yiivalidators FileValidator that expects a file:
  • 284. 278 CHAPTER 7. GETTING DATA FROM USERS $file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE //in code framework If validation is successful, then we’re saving the file: $model->file->saveAs(’uploads/’ . $model->file->baseName . ’.’ . $model-> file->extension); If you’re using “basic” application template then folder uploads should be created under web. That’s it. Load the page and try uploading. Uplaods should end up in basic/web/uploads. 7.3.4 Additional information Required rule If you need to make file upload mandatory use skipOnEmpty like the following: public function rules() { return [ [[’file’], ’file’, ’skipOnEmpty’ => false], ]; } MIME type It is wise to validate type of the file uploaded. FileValidator has property $extensions for the purpose: public function rules() { return [ [[’file’], ’file’, ’extensions’ => ’gif, jpg’,], ]; } The thing is that it validates only file extension and not the file content. In order to validate content as well use mimeTypes property of FileValidator: public function rules() { return [ [[’file’], ’file’, ’extensions’ => ’jpg, png’, ’mimeTypes’ => ’image /jpeg, image/png’,], ]; } List of common media types2 2 http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_ types
  • 285. 7.3. UPLOADING FILES 279 Validating uploaded image If you upload an image, yiivalidatorsImageValidator may come in handy. It verifies if an attribute received a valid image that can be then either saved or processed using Imagine Extension3. Uploading multiple files If you need download multiple files at once some adjustments are required. View: <?php use yiiwidgetsActiveForm; $form = ActiveForm::begin([’options’ => [’enctype’ => ’multipart/form-data’ ]]); if ($model->hasErrors()) { //it is necessary to see all the errors for all the files. echo ’<pre>’; print_r($model->getErrors()); echo ’</pre>’; } ?> <?= $form->field($model, ’file[]’)->fileInput([’multiple’ => ’’]) ?> <button>Submit</button> <?php ActiveForm::end(); ?> The difference is the following line: <?= $form->field($model, ’file[]’)->fileInput([’multiple’ => ’’]) ?> Controller: namespace appcontrollers; use Yii; use yiiwebController; use appmodelsUploadForm; use yiiwebUploadedFile; class SiteController extends Controller { public function actionUpload() { $model = new UploadForm(); if (Yii::$app->request->isPost) { $files = UploadedFile::getInstances($model, ’file’); 3 https://github.com/yiisoft/yii2/tree/master/extensions/imagine
  • 286. 280 CHAPTER 7. GETTING DATA FROM USERS foreach ($files as $file) { $_model = new UploadForm(); $_model->file = $file; if ($_model->validate()) { $_model->file->saveAs(’uploads/’ . $_model->file-> baseName . ’.’ . $_model->file->extension); } else { foreach ($_model->getErrors(’file’) as $error) { $model->addError(’file’, $error); } } } if ($model->hasErrors(’file’)){ $model->addError( ’file’, count($model->getErrors(’file’)) . ’ of ’ . count($files ) . ’ files not uploaded’ ); } } return $this->render(’upload’, [’model’ => $model]); } } The difference is UploadedFile::getInstances($model, ’file’); instead of UploadedFile ::getInstance($model, ’file’);. Former returns instances for all uploaded files while the latter gives you only a single instance.
  • 287. 7.3. UPLOADING FILES 281 Error: not existing file: input-multiple-models.md
  • 288. 282 CHAPTER 7. GETTING DATA FROM USERS
  • 289. Chapter 8 Displaying Data 8.1 Data Formatter For formatting of outputs Yii provides a formatter class to make data more readable for users. yiii18nFormatter is a helper class that is registered as an application component named formatter by default. It provides a set of methods for data formatting purpose such as date/- time values, numbers and other commonly used formats in a localized way. The formatter can be used in two different ways. 1. Using the formatting methods (all formatter methods prefixed with as) directly: echo Yii::$app->formatter->asDate(’2014-01-01’, ’long’); // output: January 1, 2014 echo Yii::$app->formatter->asPercent(0.125, 2); // output: 12.50% echo Yii::$app->formatter->asEmail(’[email protected]’); // output: <a href="mailto:[email protected]">[email protected]</a> echo Yii::$app->formatter->asBoolean(true); // output: Yes // it also handles display of null values: echo Yii::$app->formatter->asDate(null); // output: (Not set) 2. Using the yiii18nFormatter::format() method and the format name. This method is also used by widgets like yiigridGridView and yiiwidgetsDetailView where you can specify the data format of a column in the widget configuration. echo Yii::$app->formatter->format(’2014-01-01’, ’date’); // output: January 1, 2014 // you can also use an array to specify parameters for the format method: // ‘2‘ is the value for the $decimals parameter of the asPercent()- method. echo Yii::$app->formatter->format(0.125, [’percent’, 2]); // output: 12.50% 283
  • 290. 284 CHAPTER 8. DISPLAYING DATA All output of the formatter is localized when the PHP intl extension1 is installed. You can configure the yiii18nFormatter::locale property of the formatter for this. If not configured, the application yiibaseApplication ::language is used as the locale. See the Section on internationaization for more details. The Formatter will then choose the correct format for dates and numbers according to the locale including names of month and week days translated to the current language. Date formats are also affected by the yiii18nFormatter::timeZone which will also be taken yiibase Application::timeZone if not configured explicitly. For example the date format call will output different results for different locales: Yii::$app->formatter->locale = ’en-US’; echo Yii::$app->formatter->asDate(’2014-01-01’); // output: January 1, 2014 Yii::$app->formatter->locale = ’de-DE’; echo Yii::$app->formatter->asDate(’2014-01-01’); // output: 1. Januar 2014 Yii::$app->formatter->locale = ’ru-RU’; echo Yii::$app->formatter->asDate(’2014-01-01’); // output: 1 января 2014 г. Note that formatting may differ between different versions of the ICU library compiled with PHP and also based on the fact whether the PHP intl extension2 is installed or not. So to ensure your website works with the same output in all environments it is recommended to install the PHP intl extension in all environ- ments and verify that the version of the ICU library is the same. See also: Setting up your PHP environment for internationaliza- tion. 8.1.1 Configuring the format The default format of the Formatter class can be adjusted using the proper- ties of the formatter class. You can adjust these values application wide by configuring the formatter component in your application config. An example configuration is shown in the following. For more details about the available properties check out the yiii18nFormatter. ’components’ => [ ’formatter’ => [ ’dateFormat’ => ’dd.MM.yyyy’, ’decimalSeparator’ => ’,’, ’thousandSeparator’ => ’ ’, ’currencyCode’ => ’EUR’, ]; 1 http://php.net/manual/en/book.intl.php 2 http://php.net/manual/en/book.intl.php
  • 291. 8.1. DATA FORMATTER 285 8.1.2 Formatting Dates Note: This section is under development. TDB See http://site.icu-project.org/ for the format. • yiii18nFormatter::asDate() - the value is formatted as date. • yiii18nFormatter::asTime() - the value is formatted as time. • yiii18nFormatter::asDatetime() - the value is formatted as dat- etime. • yiii18nFormatter::asTimestamp() - the value is formatted as a unix timestamp. • yiii18nFormatter::asRelativeTime() - the value is formatted as the time interval between a date and now in human readable form. The input value for date and time formatting is assumed to be in UTC unless a timezone is explicitly given. 8.1.3 Formatting Numbers Note: This section is under development. TDB See http://site.icu-project.org/ for the format. • yiii18nFormatter::asInteger() - the value is formatted as an integer. • yiii18nFormatter::asDecimal() - the value is formatted as a number with decimal and thousand separators. • yiii18nFormatter::asPercent() - the value is formatted as a per- cent number. • yiii18nFormatter::asScientific() - the value is formatted as a number in scientific format. • yiii18nFormatter::asCurrency() - the value is formatted as a currency value. • yiii18nFormatter::asSize() - the value that is a number of bytes is formatted as a human readable size. • yiii18nFormatter::asShortSize() - the value that is a number of bytes is formatted as a human readable size.
  • 292. 286 CHAPTER 8. DISPLAYING DATA 8.1.4 Other formatters Note: This section is under development. TDB Here’s the bundled formatters list: • yiii18nFormatter::asRaw() - the value is outputted as is. • yiii18nFormatter::asText() - the value is HTML-encoded. This format is used by default. • yiii18nFormatter::asNtext() - the value is formatted as an HTML- encoded plain text with newlines converted into line breaks. • yiii18nFormatter::asParagraphs() - the value is formatted as HTML-encoded text paragraphs wrapped into <p> tags. • yiii18nFormatter::asHtml() - the value is purified using HtmlPurifier to avoid XSS attacks. You can pass additional options such as [’html’ , [’Attr.AllowedFrameTargets’ => [’_blank’]]]. • yiii18nFormatter::asEmail() - the value is formatted as a mailto link. • yiii18nFormatter::asImage() - the value is formatted as an im- age tag. • yiii18nFormatter::asUrl() - the value is formatted as a hyper- link. • yiii18nFormatter::asBoolean() - the value is formatted as a boolean. You can set what’s rendered for true and false values by calling Yii::$app->formatter->booleanFormat = [’No’, ’Yes’]; before out- putting GridView.
  • 293. 8.1. DATA FORMATTER 287 Error: not existing file: output-pagination.md
  • 294. 288 CHAPTER 8. DISPLAYING DATA Error: not existing file: output-sorting.md
  • 295. 8.2. DATA PROVIDERS 289 8.2 Data providers Note: This section is under development. Data provider abstracts data set via yiidataDataProviderInterface and handles pagination and sorting. It can be used by grids, lists and other data widgets. In Yii there are three built-in data providers: yiidataActiveDataProvider, yiidataArrayDataProvider and yiidataSqlDataProvider. 8.2.1 Active data provider ActiveDataProvider provides data by performing DB queries using yiidb Query and yiidbActiveQuery. The following is an example of using it to provide ActiveRecord instances: $provider = new ActiveDataProvider([ ’query’ => Post::find(), ’pagination’ => [ ’pageSize’ => 20, ], ]); // get the posts in the current page $posts = $provider->getModels(); And the following example shows how to use ActiveDataProvider without ActiveRecord: $query = new Query(); $provider = new ActiveDataProvider([ ’query’ => $query->from(’post’), ’sort’ => [ // Set the default sort by name ASC and created_at DESC. ’defaultOrder’ => [ ’name’ => SORT_ASC, ’created_at’ => SORT_DESC ] ], ’pagination’ => [ ’pageSize’ => 20, ], ]); // get the posts in the current page $posts = $provider->getModels(); 8.2.2 Array data provider ArrayDataProvider implements a data provider based on a data array.
  • 296. 290 CHAPTER 8. DISPLAYING DATA The yiidataArrayDataProvider::$allModels property contains all data models that may be sorted and/or paginated. ArrayDataProvider will provide the data after sorting and/or pagination. You may configure the yiidataArrayDataProvider::$sort and yiidataArrayDataProvider ::$pagination properties to customize the sorting and pagination behavi- ors. Elements in the yiidataArrayDataProvider::$allModels array may be either objects (e.g. model objects) or associative arrays (e.g. query results of DAO). Make sure to set the yiidataArrayDataProvider::$key property to the name of the field that uniquely identifies a data record or false if you do not have such a field. Compared to ActiveDataProvider, ArrayDataProvider could be less efficient because it needs to have yiidataArrayDataProvider::$allModels ready. ArrayDataProvider may be used in the following way: $query = new Query(); $provider = new ArrayDataProvider([ ’allModels’ => $query->from(’post’)->all(), ’sort’ => [ ’attributes’ => [’id’, ’username’, ’email’], ], ’pagination’ => [ ’pageSize’ => 10, ], ]); // get the posts in the current page $posts = $provider->getModels(); Note: if you want to use the sorting feature, you must configure the sort property so that the provider knows which columns can be sorted. 8.2.3 SQL data provider SqlDataProvider implements a data provider based on a plain SQL state- ment. It provides data in terms of arrays, each representing a row of query result. Like other data providers, SqlDataProvider also supports sorting and pagination. It does so by modifying the given yiidataSqlDataProvider:: $sql statement with “ORDER BY” and “LIMIT” clauses. You may configure the yiidataSqlDataProvider::$sort and yiidataSqlDataProvider ::$pagination properties to customize sorting and pagination behaviors. SqlDataProvider may be used in the following way: $count = Yii::$app->db->createCommand(’ SELECT COUNT(*) FROM user WHERE status=:status ’, [’:status’ => 1])->queryScalar();
  • 297. 8.3. DATA WIDGETS 291 $dataProvider = new SqlDataProvider([ ’sql’ => ’SELECT * FROM user WHERE status=:status’, ’params’ => [’:status’ => 1], ’totalCount’ => $count, ’sort’ => [ ’attributes’ => [ ’age’, ’name’ => [ ’asc’ => [’first_name’ => SORT_ASC, ’last_name’ => SORT_ASC ], ’desc’ => [’first_name’ => SORT_DESC, ’last_name’ => SORT_DESC], ’default’ => SORT_DESC, ’label’ => ’Name’, ], ], ], ’pagination’ => [ ’pageSize’ => 20, ], ]); // get the user records in the current page $models = $dataProvider->getModels(); Note: if you want to use the pagination feature, you must con- figure the yiidataSqlDataProvider::$totalCount property to be the total number of rows (without pagination). And if you want to use the sorting feature, you must configure the yiidata SqlDataProvider::$sort property so that the provider knows which columns can be sorted. 8.2.4 Implementing your own custom data provider TBD 8.3 Data widgets Note: This section is under development. 8.3.1 ListView 8.3.2 DetailView DetailView displays the detail of a single data yiiwidgetsDetailView::$model. It is best used for displaying a model in a regular format (e.g. each model attribute is displayed as a row The model can be either an instance of yiibaseModel or an associative array.
  • 298. 292 CHAPTER 8. DISPLAYING DATA DetailView uses the yiiwidgetsDetailView::$attributes property to determines which model attributes should be displayed and how they should be formatted. A typical usage of DetailView is as follows: echo DetailView::widget([ ’model’ => $model, ’attributes’ => [ ’title’, // title attribute (in plain text) ’description:html’, // description attribute in HTML [ // the owner name of the model ’label’ => ’Owner’, ’value’ => $model->owner->name, ], ], ]); 8.3.3 GridView Data grid or GridView is one of the most powerful Yii widgets. It is extremely useful if you need to quickly build admin section of the system. It takes data from data provider and renders each row using a set of columns presenting data in a form of a table. Each row of the table represents the data of a single data item, and a column usually represents an attribute of the item (some columns may correspond to complex expression of attributes or static text). Grid view supports both sorting and pagination of the data items. The sorting and pagination can be done in AJAX mode or normal page request. A benefit of using GridView is that when the user disables JavaScript, the sorting and pagination automatically degrade to normal page requests and are still functioning as expected. The minimal code needed to use GridView is as follows: use yiigridGridView; use yiidataActiveDataProvider; $dataProvider = new ActiveDataProvider([ ’query’ => Post::find(), ’pagination’ => [ ’pageSize’ => 20, ], ]); echo GridView::widget([ ’dataProvider’ => $dataProvider, ]); The above code first creates a data provider and then uses GridView to display every attribute in every row taken from data provider. The displayed table is equipped with sorting and pagination functionality.
  • 299. 8.3. DATA WIDGETS 293 Grid columns Yii grid consists of a number of columns. Depending on column type and settings these are able to present data differently. These are defined in the columns part of GridView config like the follow- ing: echo GridView::widget([ ’dataProvider’ => $dataProvider, ’columns’ => [ [’class’ => ’yiigridSerialColumn’], // A simple column defined by the data contained in $dataProvider. // Data from model’s column1 will be used. ’id’, ’username’, // More complex one. [ ’class’ => ’yiigridDataColumn’, // can be omitted, default ’value’ => function ($data) { return $data->name; //$data[’name’] for array data, e.g. using SqlDataProvider. }, ], ], ]); Note that if columns part of config isn’t specified, Yii tries to show all possible data provider model columns. Column classes Grid columns could be customized by using different column classes: echo GridView::widget([ ’dataProvider’ => $dataProvider, ’columns’ => [ [ ’class’ => ’yiigridSerialColumn’, // <-- here // you may configure additional properties here ], Additionally to column classes provided by Yii that we’ll review below you can create your own column classes. Each column class extends from yiigridColumn so there some com- mon options you can set while configuring grid columns. • header allows to set content for header row. • footer allows to set content for footer row. • visible is the column should be visible.
  • 300. 294 CHAPTER 8. DISPLAYING DATA • content allows you to pass a valid PHP callback that will return data for a row. The format is the following: function ($model, $key, $index, $column) { return ’a string’; } You may specify various container HTML options passing arrays to: • headerOptions • contentOptions • footerOptions • filterOptions Data column Data column is for displaying and sorting data. It is default column type so specifying class could be omitted when using it. The main setting of the data column is its format. It could be spe- cified via format attribute. Its values are corresponding to methods in format application component that is yiii18nFormatter by default: <?= GridView::widget([ ’columns’ => [ [ ’attribute’ => ’name’, ’format’ => ’text’ ], [ ’attribute’ => ’birthday’, ’format’ => [’date’, ’Y-m-d’] ], ], ]); ?> In the above text corresponds to yiii18nFormatter::asText(). The value of the column is passed as the first argument. In the second column definition date corresponds to yiii18nFormatter::asDate(). The value of the column is, again, passed as the first argument while ‘Y-m-d’ is used as the second argument value. For a list of available formatters see the section about Data Formatting. Action column Action column displays action buttons such as update or delete for each row. echo GridView::widget([ ’dataProvider’ => $dataProvider, ’columns’ => [ [
  • 301. 8.3. DATA WIDGETS 295 ’class’ => ’yiigridActionColumn’, // you may configure additional properties here ], Available properties you can configure are: • controller is the ID of the controller that should handle the actions. If not set, it will use the currently active controller. • template the template used for composing each cell in the action column. Tokens enclosed within curly brackets are treated as controller action IDs (also called button names in the context of action column). They will be replaced by the corresponding button rendering callbacks spe- cified in yiigridActionColumn::$buttons. For example, the token {view} will be replaced by the result of the callback buttons[’view’]. If a callback cannot be found, the token will be replaced with an empty string. Default is {view} {update} {delete}. • buttons is an array of button rendering callbacks. The array keys are the button names (without curly brackets), and the values are the corresponding button rendering callbacks. The callbacks should use the following signature: function ($url, $model) { // return the button HTML code } In the code above $url is the URL that the column creates for the button, and $model is the model object being rendered for the current row. • urlCreator is a callback that creates a button URL using the specified model information. The signature of the callback should be the same as that of yiigridActionColumn::createUrl(). If this property is not set, button URLs will be created using yiigridActionColumn ::createUrl(). Checkbox column CheckboxColumn displays a column of checkboxes. To add a CheckboxColumn to the yiigridGridView, add it to the yii gridGridView::$columns configuration as follows: echo GridView::widget([ ’dataProvider’ => $dataProvider, ’columns’ => [ // ... [ ’class’ => ’yiigridCheckboxColumn’, // you may configure additional properties here ], ],
  • 302. 296 CHAPTER 8. DISPLAYING DATA Users may click on the checkboxes to select rows of the grid. The selected rows may be obtained by calling the following JavaScript code: var keys = $(’#grid’).yiiGridView(’getSelectedRows’); // keys is an array consisting of the keys associated with the selected rows Serial column Serial column renders row numbers starting with 1 and going forward. Usage is as simple as the following: echo GridView::widget([ ’dataProvider’ => $dataProvider, ’columns’ => [ [’class’ => ’yiigridSerialColumn’], // <-- here // ... Sorting data • https://github.com/yiisoft/yii2/issues/1576 Filtering data For filtering data the GridView needs a model that takes the input from the filtering form and adjusts the query of the dataProvider to respect the search criteria. A common practice when using active records is to create a search Model class that provides needed functionality (it can be generated for you by Gii). This class defines the validation rules for the search and provides a search() method that will return the data provider. To add search capability for the Post model we can create PostSearch like in the following example: <?php namespace appmodels; use Yii; use yiibaseModel; use yiidataActiveDataProvider; class PostSearch extends Post { public function rules() { // only fields in rules() are searchable return [ [[’id’], ’integer’], [[’title’, ’creation_date’], ’safe’], ]; }
  • 303. 8.3. DATA WIDGETS 297 public function scenarios() { // bypass scenarios() implementation in the parent class return Model::scenarios(); } public function search($params) { $query = Post::find(); $dataProvider = new ActiveDataProvider([ ’query’ => $query, ]); // load the seach form data and validate if (!($this->load($params) && $this->validate())) { return $dataProvider; } // adjust the query by adding the filters $query->andFilterWhere([’id’ => $this->id]); $query->andFilterWhere([’like’, ’title’, $this->name]) ->andFilterWhere([’like’, ’creation_date’, $this-> creation_date]); return $dataProvider; } } You can use this function in the controller to get the dataProvider for the GridView: $searchModel = new PostSearch(); $dataProvider = $searchModel->search(Yii::$app->request->get()); return $this->render(’myview’, [ ’dataProvider’ => $dataProvider, ’searchModel’ => $searchModel, ]); And in the view you then assign the $dataProvider and $searchModel to the GridView: echo GridView::widget([ ’dataProvider’ => $dataProvider, ’filterModel’ => $searchModel, ]); Working with model relations When displaying active records in a GridView you might encounter the case where you display values of related columns such as the post’s author’s name instead of just his id. You do this by defining the attribute name in columns
  • 304. 298 CHAPTER 8. DISPLAYING DATA as author.name when the Post model has a relation named author and the author model has an attribute name. The GridView will then display the name of the author but sorting and filtering are not enabled by default. You have to adjust the PostSearch model that has been introduced in the last section to add this functionality. To enable sorting on a related column you have to join the related table and add the sorting rule to the Sort component of the data provider: $query = Post::find(); $dataProvider = new ActiveDataProvider([ ’query’ => $query, ]); // join with relation ‘author‘ that is a relation to the table ‘users‘ // and set the table alias to be ‘author‘ $query->joinWith([’author’ => function($query) { $query->from([’author’ => ’ users’]); }]); // enable sorting for the related column $dataProvider->sort->attributes[’author.name’] = [ ’asc’ => [’author.name’ => SORT_ASC], ’desc’ => [’author.name’ => SORT_DESC], ]; // ... Filtering also needs the joinWith call as above. You also need to define the searchable column in attributes and rules like this: public function attributes() { // add related fields to searchable attributes return array_merge(parent::attributes(), [’author.name’]); } public function rules() { return [ [[’id’], ’integer’], [[’title’, ’creation_date’, ’author.name’], ’safe’], ]; } In search() you then just add another filter condition with: $query->andFilterWhere([’LIKE’, ’author.name’, $this->getAttribute(’author. name’)]); Info: In the above we use the same string for the relation name and the table alias, however when your alias and relation name differ, you have to pay attention on where to use the alias and where to use the relation name. A simple rule for this is to use the alias in every place that is used to build the database query
  • 305. 8.3. DATA WIDGETS 299 and the relation name in all other definitions like in attributes() and rules() etc. For example you use the alias au for the author relation table, the joinWith statement looks like the following: $query->joinWith([’author’ => function($query) { $query->from([’ au’ => ’users’]); }]); It is also possible to just call $query->joinWith([’author’]); when the alias is defined in the relation definition. The alias has to be used in the filter condition but the attribute name stays the same: $query->andFilterWhere([’LIKE’, ’au.name’, $this->getAttribute(’ author.name’)]); Same is true for the sorting definition: $dataProvider->sort->attributes[’author.name’] = [ ’asc’ => [’au.name’ => SORT_ASC], ’desc’ => [’au.name’ => SORT_DESC], ]; Also when specifying the yiidataSort::defaultOrder for sort- ing you need to use the relation name instead of the alias: $dataProvider->sort->defaultOrder = [’author.name’ => SORT_ASC]; Info: For more information on joinWith and the queries performed in the background, check the active record docs on eager and lazy loading. Using sql views for filtering, sorting and displaying data There is also other approach that can be faster and more useful - sql views. So for example if we need to show gridview with users and their profiles we can do it in this way: CREATE OR REPLACE VIEW vw_user_info AS SELECT user.*, user_profile.lastname, user_profile.firstname FROM user, user_profile WHERE user.id = user_profile.user_id Then you need to create ActiveRecord that will be representing this view: namespace appmodelsviewsgrid; use yiidbActiveRecord; class UserView extends ActiveRecord {
  • 306. 300 CHAPTER 8. DISPLAYING DATA /** * @inheritdoc */ public static function tableName() { return ’vw_user_info’; } public static function primaryKey() { return [’id’]; } /** * @inheritdoc */ public function rules() { return [ // define here your rules ]; } /** * @inheritdoc */ public static function attributeLabels() { return [ // define here your attribute labels ]; } } After that you can youse this UserView active record with search models, without additional specifying of sorting and filtering attributes. All attrib- utes will be working out of the box. Note that this approach has several pros and cons: • you dont need to specify different sorting and filtering conditions and other things. Everything works out of the box; • it can be much faster because of data size, count of sql queries per- formed (for each relation you will need additional query); • since this is a just simple mupping UI on sql view it lacks of some domain logic that is in your entities, so if you will have some methods like isActive, isDeleted or other that will influence on UI you will need to duplicate them in this class too.
  • 307. 8.4. WORKING WITH CLIENT SCRIPTS 301 Multiple GridViews on one page You can use more than one GridView on a single page but some additional configuration is needed so that they do not interfere. When using multiple instances of GridView you have to configure different parameter names for the generated sort and pagination links so that each GridView has its in- dividual sorting and pagination. You do so by setting the yiidataSort ::sortParam and yiidataPagination::pageParam of the dataProviders yiidataBaseDataProvider::$sort and yiidataBaseDataProvider:: $pagination instance. Assume we want to list Post and User models for which we have already prepared two data providers in $userProvider and $postProvider: use yiigridGridView; $userProvider->pagination->pageParam = ’user-page’; $userProvider->sort->sortParam = ’user-sort’; $postProvider->pagination->pageParam = ’post-page’; $postProvider->sort->sortParam = ’post-sort’; echo ’<h1>Users</h1>’; echo GridView::widget([ ’dataProvider’ => $userProvider, ]); echo ’<h1>Posts</h1>’; echo GridView::widget([ ’dataProvider’ => $postProvider, ]); Using GridView with Pjax TBD 8.4 Working with Client Scripts Note: This section is under development. Registering scripts With the yiiwebView object you can register scripts. There are two ded- icated methods for it: yiiwebView::registerJs() for inline scripts and yiiwebView::registerJsFile() for external scripts. Inline scripts are useful for configuration and dynamically generated code. The method for adding these can be used as follows: $this->registerJs("var options = ".json_encode($options).";", View::POS_END, ’my-options’);
  • 308. 302 CHAPTER 8. DISPLAYING DATA The first argument is the actual JS code we want to insert into the page. The second argument determines where script should be inserted into the page. Possible values are: • yiiwebView::POS_HEAD for head section. • yiiwebView::POS_BEGIN for right after opening <body>. • yiiwebView::POS_END for right before closing </body>. • yiiwebView::POS_READY for executing code on document ready event. This will register yiiwebJqueryAsset automatically. • yiiwebView::POS_LOAD for executing code on document load event. This will register yiiwebJqueryAsset automatically. The last argument is a unique script ID that is used to identify code block and replace existing one with the same ID instead of adding a new one. If you don’t provide it, the JS code itself will be used as the ID. An external script can be added like the following: $this->registerJsFile(’http://example.com/js/main.js’, [’depends’ => [ JqueryAsset::className()]]); The arguments for yiiwebView::registerJsFile() are similar to those for yiiwebView::registerCssFile(). In the above example, we register the main.js file with the dependency on JqueryAsset. This means the main.js file will be added AFTER jquery.js. Without this dependency specification, the relative order between main.js and jquery.js would be undefined. Like for yiiwebView::registerCssFile(), it is also highly recom- mended that you use asset bundles to register external JS files rather than using yiiwebView::registerJsFile(). Registering asset bundles As was mentioned earlier it’s preferred to use asset bundles instead of using CSS and JavaScript directly. You can get details on how to define asset bundles in asset manager section of the guide. As for using already defined asset bundle, it’s very straightforward: frontendassetsAppAsset::register($this); Registering CSS You can register CSS using yiiwebView::registerCss() or yiiwebView ::registerCssFile(). The former registers a block of CSS code while the latter registers an external CSS file. For example, $this->registerCss("body { background: #f00; }");
  • 309. 8.5. THEMING 303 The code above will result in adding the following to the head section of the page: <style> body { background: #f00; } </style> If you want to specify additional properties of the style tag, pass an array of name-values to the third argument. If you need to make sure there’s only a single style tag use fourth argument as was mentioned in meta tags description. $this->registerCssFile("http://example.com/css/themes/black-and-white.css", [ ’depends’ => [BootstrapAsset::className()], ’media’ => ’print’, ], ’css-print-theme’); The code above will add a link to CSS file to the head section of the page. • The first argument specifies the CSS file to be registered. • The second argument specifies the HTML attributes for the resulting <link> tag. The option depends is specially handled. It specifies which asset bundles this CSS file depends on. In this case, the dependent asset bundle is yiibootstrapBootstrapAsset. This means the CSS file will be added after the CSS files in yiibootstrapBootstrapAsset. • The last argument specifies an ID identifying this CSS file. If it is not provided, the URL of the CSS file will be used instead. It is highly recommended that you use asset bundles to register external CSS files rather than using yiiwebView::registerCssFile(). Using asset bundles allows you to combine and compress multiple CSS files, which is desirable for high traffic websites. 8.5 Theming Note: This section is under development. A theme is a directory of view and layout files. Each file of the theme over- rides corresponding file of an application when rendered. A single application may use multiple themes and each may provide totally different experience. At any time only one theme can be active. Note: Themes usually do not meant to be redistributed since views are too application specific. If you want to redistribute customized look and feel consider CSS and JavaScript files in form of asset bundles instead.
  • 310. 304 CHAPTER 8. DISPLAYING DATA 8.5.1 Configuring a theme Theme configuration is specified via view component of the application. In order to set up a theme to work with basic application views the following should be in your application config file: ’components’ => [ ’view’ => [ ’theme’ => [ ’pathMap’ => [’@app/views’ => ’@app/themes/basic’], ’baseUrl’ => ’@web/themes/basic’, ], ], ], In the above pathMap defines a map of original paths to themed paths while baseUrl defines base URL for resources referenced from theme files. In our case pathMap is [’@app/views’ => ’@app/themes/basic’]. That means that every view in @app/views will be first searched under @app/themes/basic and if a view exists in the theme directory it will be used instead of the original view. For example, with a configuration above a themed version of a view file @app/views/site/index.php will be @app/themes/basic/site/index.php. It basic- ally replaces @app/views in @app/views/site/index.php with @app/themes/basic. Theming modules In order to theme modules pathMap may look like the following: ’components’ => [ ’view’ => [ ’theme’ => [ ’pathMap’ => [ ’@app/views’ => ’@app/themes/basic’, ’@app/modules’ => ’@app/themes/basic/modules’, // <-- !!! ], ], ], ], It will allow you to theme @app/modules/blog/views/comment/index.php with @app /themes/basic/modules/blog/views/comment/index.php. Theming widgets In order to theme a widget view located at @app/widgets/currency/views/index .php you need the following config for view component theme: ’components’ => [ ’view’ => [ ’theme’ => [ ’pathMap’ => [’@app/widgets’ => ’@app/themes/basic/widgets’],
  • 311. 8.5. THEMING 305 ], ], ], With the config above you can create themed version of @app/widgets/currency /index.php view in @app/themes/basic/widgets/currency/index.php. 8.5.2 Using multiple paths It is possible to map a single path to multiple theme paths. For example, ’pathMap’ => [ ’@app/views’ => [ ’@app/themes/christmas’, ’@app/themes/basic’, ], ] In this case, the view will be searched in @app/themes/christmas/site/index.php then if it’s not found it will check @app/themes/basic/site/index.php. If there’s no view there as well application view will be used. This ability is especially useful if you want to temporary or conditionally override some views.
  • 312. 306 CHAPTER 8. DISPLAYING DATA
  • 313. Chapter 9 Security 9.1 Authentication Note: This section is under development. Authentication is the act of verifying who a user is, and is the basis of the lo- gin process. Typically, authentication uses the combination of an identifier–a username or email address–and a password. The user submits these values through a form, and the application then compares the submitted informa- tion against that previously stored (e.g., upon registration). In Yii, this entire process is performed semi-automatically, leaving the developer to merely implement yiiwebIdentityInterface, the most im- portant class in the authentication system. Typically, implementation of IdentityInterface is accomplished using the User model. You can find a fully featured example of authentication in the advanced application template. Below, only the interface methods are listed: class User extends ActiveRecord implements IdentityInterface { // ... /** * Finds an identity by the given ID. * * @param string|integer $id the ID to be looked for * @return IdentityInterface|null the identity object that matches the given ID. */ public static function findIdentity($id) { return static::findOne($id); } /** * Finds an identity by the given token. * 307
  • 314. 308 CHAPTER 9. SECURITY * @param string $token the token to be looked for * @return IdentityInterface|null the identity object that matches the given token. */ public static function findIdentityByAccessToken($token, $type = null) { return static::findOne([’access_token’ => $token]); } /** * @return int|string current user ID */ public function getId() { return $this->id; } /** * @return string current user auth key */ public function getAuthKey() { return $this->auth_key; } /** * @param string $authKey * @return boolean if auth key is valid for current user */ public function validateAuthKey($authKey) { return $this->getAuthKey() === $authKey; } } Two of the outlined methods are simple: findIdentity is provided with an ID value and returns a model instance associated with that ID. The getId method returns the ID itself. Two of the other methods – getAuthKey and validateAuthKey – are used to provide extra security to the “remember me” cookie. The getAuthKey method should return a string that is unique for each user. You can reliably create a unique string using Yii::$app->getSecurity ()->generateRandomString(). It’s a good idea to also save this as part of the user’s record: public function beforeSave($insert) { if (parent::beforeSave($insert)) { if ($this->isNewRecord) { $this->auth_key = Yii::$app->getSecurity()->generateRandomString (); } return true; }
  • 315. 9.2. AUTHORIZATION 309 return false; } The validateAuthKey method just needs to compare the $authKey variable, passed as parameter (itself retrieved from a cookie), with the value fetched from database. 9.2 Authorization Note: This section is under development. Authorization is the process of verifying that a user has enough permission to do something. Yii provides two authorization methods: Access Control Filter (ACF) and Role-Based Access Control (RBAC). 9.2.1 Access Control Filter Access Control Filter (ACF) is a simple authorization method that is best used by applications that only need some simple access control. As its name indicates, ACF is an action filter that can be attached to a controller or a module as a behavior. ACF will check a set of yiifiltersAccessControl ::rules to make sure the current user can access the requested action. The code below shows how to use ACF which is implemented as yii filtersAccessControl: use yiifiltersAccessControl; class SiteController extends Controller { public function behaviors() { return [ ’access’ => [ ’class’ => AccessControl::className(), ’only’ => [’login’, ’logout’, ’signup’], ’rules’ => [ [ ’allow’ => true, ’actions’ => [’login’, ’signup’], ’roles’ => [’?’], ], [ ’allow’ => true, ’actions’ => [’logout’], ’roles’ => [’@’], ], ], ], ]; }
  • 316. 310 CHAPTER 9. SECURITY // ... } In the code above ACF is attached to the site controller as a behavior. This is the typical way of using an action filter. The only option specifies that the ACF should only be applied to login, logout and signup actions. The rules option specifies the yiifiltersAccessRule, which reads as follows: • Allow all guest (not yet authenticated) users to access ‘login’ and ‘signup’ actions. The roles option contains a question mark ? which is a special token recognized as “guests”. • Allow authenticated users to access ‘logout’ action. The @ character is another special token recognized as authenticated users. When ACF performs authorization check, it will examine the rules one by one from top to bottom until it finds a match. The allow value of the matching rule will then be used to judge if the user is authorized. If none of the rules matches, it means the user is NOT authorized and ACF will stop further action execution. By default, ACF does only of the followings when it determines a user is not authorized to access the current action: • If the user is a guest, it will call yiiwebUser::loginRequired(), which may redirect the browser to the login page. • If the user is already authenticated, it will throw a yiiwebForbiddenHttpException. You may customize this behavior by configuring the yiifiltersAccessControl ::denyCallback property: [ ’class’ => AccessControl::className(), ’denyCallback’ => function ($rule, $action) { throw new Exception(’You are not allowed to access this page’); } ] yiifiltersAccessRule support many options. Below is a summary of the supported options. You may also extend yiifiltersAccessRule to create your own customized access rule classes. • yiifiltersAccessRule::allow: specifies whether this is an “allow” or “deny” rule. • yiifiltersAccessRule::actions: specifies which actions this rule matches. This should be an array of action IDs. The comparison is case-sensitive. If this option is empty or not set, it means the rule applies to all actions.
  • 317. 9.2. AUTHORIZATION 311 • yiifiltersAccessRule::controllers: specifies which controllers this rule matches. This should be an array of controller IDs. The comparison is case-sensitive. If this option is empty or not set, it means the rule applies to all controllers. • yiifiltersAccessRule::roles: specifies which user roles that this rule matches. Two special roles are recognized, and they are checked via yiiwebUser::isGuest: - ?: matches a guest user (not authen- ticated yet) - @: matches an authenticated user Using other role names requires RBAC (to be described in the next section), and yiiweb User::can() will be called. If this option is empty or not set, it means this rule applies to all roles. • yiifiltersAccessRule::ips: specifies which yiiwebRequest:: userIP this rule matches. An IP address can contain the wildcard * at the end so that it matches IP addresses with the same prefix. For example, ‘192.168.*‘ matches all IP addresses in the segment ‘192.168.’. If this option is empty or not set, it means this rule applies to all IP addresses. • yiifiltersAccessRule::verbs: specifies which request method (e.g. GET, POST) this rule matches. The comparison is case-insensitive. • yiifiltersAccessRule::matchCallback: specifies a PHP callable that should be called to determine if this rule should be applied. • yiifiltersAccessRule::denyCallback: specifies a PHP callable that should be called when this rule will deny the access. Below is an example showing how to make use of the matchCallback option, which allows you to write arbitrary access check logic: use yiifiltersAccessControl; class SiteController extends Controller { public function behaviors() { return [ ’access’ => [ ’class’ => AccessControl::className(), ’only’ => [’special-callback’], ’rules’ => [ [ ’actions’ => [’special-callback’], ’allow’ => true, ’matchCallback’ => function ($rule, $action) { return date(’d-m’) === ’31-10’; } ],
  • 318. 312 CHAPTER 9. SECURITY ], ], ]; } // Match callback called! This page can be accessed only each October 31 st public function actionSpecialCallback() { return $this->render(’happy-halloween’); } } 9.2.2 Role based access control (RBAC) Role-Based Access Control (RBAC) provides a simple yet powerful cent- ralized access control. Please refer to the Wiki article1 for details about comparing RBAC with other more traditional access control schemes. Yii implements a General Hierarchical RBAC, following the NIST RBAC model2. It provides the RBAC functionality through the yiirbacManagerInterface application component. Using RBAC involves two parts of work. The first part is to build up the RBAC authorization data, and the second part is to use the authorization data to perform access check in places where it is needed. To facilitate our description next, we will first introduce some basic RBAC concepts. Basic Concepts A role represents a collection of permissions (e.g. creating posts, updating posts). A role may be assigned to one or multiple users. To check if a user has a specified permission, we may check if the user is assigned with a role that contains that permission. Associated with each role or permission, there may be a rule. A rule represents a piece of code that will be executed during access check to de- termine if the corresponding role or permission applies to the current user. For example, the “update post” permission may have a rule that checks if the current user is the post creator. During access checking, if the user is NOT the post creator, he/she will be considered not having the “update post” permission. Both roles and permissions can be organized in a hierarchy. In particular, a role may consist of other roles or permissions; and a permission may consist of other permissions. Yii implements a partial order hierarchy which includes 1 http://en.wikipedia.org/wiki/Role-based_access_control 2 http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf
  • 319. 9.2. AUTHORIZATION 313 the more special tree hierarchy. While a role can contain a permission, it is not true vice versa. Configuring RBAC Manager Before we set off to define authorization data and perform access checking, we need to configure the yiibaseApplication::authManager application component. Yii provides two types of authorization managers: yiirbac PhpManager and yiirbacDbManager. The former uses a PHP script file to store authorization data, while the latter stores authorization data in database. You may consider using the former if your application does not require very dynamic role and permission management. The following code shows how to configure authManager in the application configuration: return [ // ... ’components’ => [ ’authManager’ => [ ’class’ => ’yiirbacPhpManager’, ], // ... ], ]; The authManager can now be accessed via Yii::$app->authManager. Tip: By default, yiirbacPhpManager stores RBAC data in the file @app/data/rbac.php. Sometime you need to create this file manually. Building Authorization Data Building authorization data is all about the following tasks: • defining roles and permissions; • establishing relations among roles and permissions; • defining rules; • associating rules with roles and permissions; • assigning roles to users. Depending on authorization flexibility requirements the tasks above could be done in different ways. If your permissions hierarchy doesn’t change at all and you have a fixed number of users you can create a console command that will initialize au- thorization data once via APIs offered by authManager:
  • 320. 314 CHAPTER 9. SECURITY <?php namespace appcommands; use Yii; use yiiconsoleController; class RbacController extends Controller { public function actionInit() { $auth = Yii::$app->authManager; // add "createPost" permission $createPost = $auth->createPermission(’createPost’); $createPost->description = ’Create a post’; $auth->add($createPost); // add "updatePost" permission $updatePost = $auth->createPermission(’updatePost’); $updatePost->description = ’Update post’; $auth->add($updatePost); // add "author" role and give this role the "createPost" permission $author = $auth->createRole(’author’); $auth->add($author); $auth->addChild($author, $createPost); // add "admin" role and give this role the "updatePost" permission // as well as the permissions of the "author" role $admin = $auth->createRole(’admin’); $auth->add($admin); $auth->addChild($admin, $updatePost); $auth->addChild($admin, $author); // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId() // usually implemented in your User model. $auth->assign($author, 2); $auth->assign($admin, 1); } } After executing the command we’ll get the following hierarchy:
  • 321. 9.2. AUTHORIZATION 315 Author can create post, admin can update post and do everything author can.
  • 322. 316 CHAPTER 9. SECURITY If your application allows user signup you need to assign roles to these new users once. For example, in order for all signed up users to become authors you in advanced application template you need to modify frontend modelsSignupForm::signup() as follows: public function signup() { if ($this->validate()) { $user = new User(); $user->username = $this->username; $user->email = $this->email; $user->setPassword($this->password); $user->generateAuthKey(); $user->save(false); // the following three lines were added: $auth = Yii::$app->authManager; $authorRole = $auth->getRole(’author’); $auth->assign($authorRole, $user->getId()); return $user; } return null; } For applications that require complex access control with dynamically up- dated authorization data, special user interfaces (i.e. admin panel) may need to be developed using APIs offered by authManager. Tip: By default, yiirbacPhpManager stores RBAC data in the file @app/data/rbac.php. Sometimes when you want to make some minor changes to the RBAC data, you may directly edit this file. Using Rules As aforementioned, rules add additional constraint to roles and permissions. A rule is a class extending from yiirbacRule. It must implement the yii rbacRule::execute() method. In the hierarchy we’ve created previously author cannot edit his own post. Let’s fix it. First we need a rule to verify that the user is the post author: namespace apprbac; use yiirbacRule; /** * Checks if authorID matches user passed via params */ class AuthorRule extends Rule { public $name = ’isAuthor’;
  • 323. 9.2. AUTHORIZATION 317 /** * @param string|integer $user the user ID. * @param Item $item the role or permission that this rule is associated with * @param array $params parameters passed to ManagerInterface:: checkAccess(). * @return boolean a value indicating whether the rule permits the role or permission it is associated with. */ public function execute($user, $item, $params) { return isset($params[’post’]) ? $params[’post’]->createdBy == $user : false; } } The rule above checks if the post is created by $user. We’ll create a special permission updateOwnPost in the command we’ve used previously: // add the rule $rule = new apprbacAuthorRule; $auth->add($rule); // add the "updateOwnPost" permission and associate the rule with it. $updateOwnPost = $this->auth->createPermission(’updateOwnPost’); $updateOwnPost->description = ’Update own post’; $updateOwnPost->ruleName = $rule->name; $auth->add($updateOwnPost); // "updateOwnPost" will be used from "updatePost" $auth->addChild($updateOwnPost, $updatePost); // allow "author" to update their own posts $auth->addChild($author, $updateOwnPost); Now we’ve got the following hierarchy:
  • 324. 318 CHAPTER 9. SECURITY Access Check With the authorization data ready, access check is as simple as a call to the yiirbacManagerInterface::checkAccess() method. Because most access check is about the current user, for convenience Yii provides a shortcut method yiiwebUser::can(), which can be used like the following: if (Yii::$app->user->can(’createPost’)) { // create post } If the current user is Jane with ID=1 we’re starting at createPost and trying to get to Jane:
  • 325. 9.2. AUTHORIZATION 319 In order to check if user can update post we need to pass an extra para- meter that is required by the AuthorRule described before: if (Yii::$app->user->can(’updatePost’, [’post’ => $post])) { // update post } Here’s what happens if current user is John:
  • 326. 320 CHAPTER 9. SECURITY We’re starting with the updatePost and going through updateOwnPost. In order to pass it AuthorRule should return true from its execute method. The method receives its $params from can method call so the value is [’post’ => $post]. If everything is OK we’re getting to author that is assigned to John. In case of Jane it is a bit simpler since she’s an admin:
  • 327. 9.2. AUTHORIZATION 321 Using Default Roles A default role is a role that is implicitly assigned to all users. The call to yii rbacManagerInterface::assign() is not needed, and the authorization data does not contain its assignment information. A default role is usually associated with a rule which determines if the role applies to the user being checked. Default roles are often used in applications which already have some sort of role assignment. For example, an application may have a “group” column in its user table to represent which privilege group each user belongs to. If each privilege group can be mapped to a RBAC role, you can use the default role feature to automatically assign each user to a RBAC role. Let’s use an example to show how this can be done. Assume in the user table, you have a group column which uses 1 to rep- resent the administrator group and 2 the author group. You plan to have two RBAC roles admin and author to represent the permissions for these two
  • 328. 322 CHAPTER 9. SECURITY groups, respectively. You can create set up the RBAC data as follows, namespace apprbac; use Yii; use yiirbacRule; /** * Checks if user group matches */ class UserGroupRule extends Rule { public $name = ’userGroup’; public function execute($user, $item, $params) { if (!Yii::$app->user->isGuest) { $group = Yii::$app->user->identity->group; if ($item->name === ’admin’) { return $group == 1; } elseif ($item->name === ’author’) { return $group == 1 || $group == 2; } } return false; } } $rule = new apprbacUserGroupRule; $auth->add($rule); $author = $auth->createRole(’author’); $author->ruleName = $rule->name; $auth->add($author); // ... add permissions as children of $author ... $admin = $auth->createRole(’admin’); $admin->ruleName = $rule->name; $auth->add($admin); $auth->addChild($admin, $author); // ... add permissions as children of $admin ... Note that in the above, because “author” is added as a child of “admin”, when you implement the execute() method of the rule class, you need to respect this hierarchy as well. That is why when the role name is “author”, the execute() method will return true if the user group is either 1 or 2 (meaning the user is in either “admin” group or “author” group). Next, configure authManager by listing the two roles in yiirbacBaseManager ::$defaultRoles: return [ // ... ’components’ => [ ’authManager’ => [
  • 329. 9.3. SECURITY 323 ’class’ => ’yiirbacPhpManager’, ’defaultRoles’ => [’admin’, ’author’], ], // ... ], ]; Now if you perform an access check, both of the admin and author roles will be checked by evaluating the rules associated with them. If the rule returns true, it means the role applies to the current user. Based on the above rule implementation, this means if the group value of a user is 1, the admin role would apply to the user; and if the group value is 2, the author role would apply. 9.3 Security Note: This section is under development. Good security is vital to the health and success of any application. Unfortu- nately, many developers cut corners when it comes to security, either due to a lack of understanding or because implementation is too much of a hurdle. To make your Yii powered application as secure as possible, Yii has included several excellent and easy to use security features. 9.3.1 Hashing and verifying passwords Most developers know that passwords cannot be stored in plain text, but many developers believe it’s still safe to hash passwords using md5 or sha1. There was a time when using the aforementioned hashing algorithms was sufficient, but modern hardware makes it possible to reverse such hashes very quickly using brute force attacks. In order to provide increased security for user passwords, even in the worst case scenario (your application is breached), you need to use a hashing algorithm that is resilient against brute force attacks. The best current choice is bcrypt. In PHP, you can create a bcrypt hash using the crypt function3. Yii provides two helper functions which make using crypt to securely generate and verify hashes easier. When a user provides a password for the first time (e.g., upon registra- tion), the password needs to be hashed: $hash = Yii::$app->getSecurity()->generatePasswordHash($password); The hash can then be associated with the corresponding model attribute, so it can be stored in the database for later use. When a user attempts to log in, the submitted password must be verified against the previously hashed and stored password: 3 http://php.net/manual/en/function.crypt.php
  • 330. 324 CHAPTER 9. SECURITY if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // all good, logging user in } else { // wrong password } 9.3.2 Generating Pseudorandom data Pseudorandom data is useful in many situations. For example when resetting a password via email you need to generate a token, save it to the database, and send it via email to end user which in turn will allow them to prove ownership of that account. It is very important that this token be unique and hard to guess, else there is a possibility that attacker can predict the token’s value and reset the user’s password. Yii security helper makes generating pseudorandom data simple: $key = Yii::$app->getSecurity()->generateRandomString(); Note that you need to have the openssl extension installed in order to gen- erate cryptographically secure random data. 9.3.3 Encryption and decryption Yii provides convenient helper functions that allow you to encrypt/decrypt data using a secret key. The data is passed through the encryption function so that only the person which has the secret key will be able to decrypt it. For example, we need to store some information in our database but we need to make sure only the user which has the secret key can view it (even if the application database is compromised): // $data and $secretKey are obtained from the form $encryptedData = Yii::$app->getSecurity()->encrypt($data, $secretKey); // store $encryptedData to database Subsequently when user wants to read the data: // $secretKey is obtained from user input, $encryptedData is from the database $data = Yii::$app->getSecurity()->decrypt($encryptedData, $secretKey); 9.3.4 Confirming data integrity There are situations in which you need to verify that your data hasn’t been tampered with by a third party or even corrupted in some way. Yii provides an easy way to confirm data integrity in the form of two helper functions. Prefix the data with a hash generated from the secret key and data // $secretKey our application or user secret, $genuineData obtained from a reliable source $data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey);
  • 331. 9.3. SECURITY 325 Checks if the data integrity has been compromised // $secretKey our application or user secret, $data obtained from an unreliable source $data = Yii::$app->getSecurity()->validateData($data, $secretKey); todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide You also can disable CSRF validation per controller and/or action, by setting its property: namespace appcontrollers; use yiiwebController; class SiteController extends Controller { public $enableCsrfValidation = false; public function actionIndex() { // CSRF validation will not be applied to this and other actions } } To disable CSRF validation per custom actions you can do: namespace appcontrollers; use yiiwebController; class SiteController extends Controller { public function beforeAction($action) { // ...set ‘$this->enableCsrfValidation‘ here based on some conditions... // call parent method that will check CSRF if such property is true. return parent::beforeAction($action); } } 9.3.5 Securing Cookies • validation • httpOnly is default 9.3.6 See also • Views security
  • 332. 326 CHAPTER 9. SECURITY Error: not existing file: security-auth-clients.md
  • 333. 9.3. SECURITY 327 Error: not existing file: security-best-practices.md
  • 334. 328 CHAPTER 9. SECURITY
  • 335. Chapter 10 Caching 10.1 Caching Caching is a cheap and effective way to improve the performance of a Web application. By storing relatively static data in cache and serving it from cache when requested, the application saves the time that would be required to generate the data from scratch every time. Caching can occur at different levels and places in a Web application. On the server side, at the lower level, cache may be used to store basic data, such as a list of most recent article information fetched from database; and at the higher level, cache may be used to store fragments or whole of Web pages, such as the rendering result of the most recent articles. On the client side, HTTP caching may be used to keep most recently visited page content in the browser cache. Yii supports all these caching mechanisms: • Data caching • Fragment caching • Page caching • HTTP caching 10.2 Data Caching Data caching is about storing some PHP variable in cache and retrieving it later from cache. It is also the foundation for more advanced caching features, such as query caching and page caching. The following code is a typical usage pattern of data caching, where $cache refers to a cache component: 329
  • 336. 330 CHAPTER 10. CACHING // try retrieving $data from cache $data = $cache->get($key); if ($data === false) { // $data is not found in cache, calculate it from scratch // store $data in cache so that it can be retrieved next time $cache->set($key, $data); } // $data is available here 10.2.1 Cache Components Data caching relies on the so-called cache components which represent vari- ous cache storage, such as memory, files, databases. Cache components are usually registered as application components so that they can be globally configurable and accessible. The following code shows how to configure the cache application component to use memcached1 with two cache servers: ’components’ => [ ’cache’ => [ ’class’ => ’yiicachingMemCache’, ’servers’ => [ [ ’host’ => ’server1’, ’port’ => 11211, ’weight’ => 100, ], [ ’host’ => ’server2’, ’port’ => 11211, ’weight’ => 50, ], ], ], ], You can then access the above cache component using the expression Yii:: $app->cache. Because all cache components support the same set of APIs, you can swap the underlying cache component with a different one by reconfiguring it in the application configuration without modifying the code that uses the cache. For example, you can modify the above configuration to use yii cachingApcCache: ’components’ => [ ’cache’ => [ 1 http://memcached.org/
  • 337. 10.2. DATA CACHING 331 ’class’ => ’yiicachingApcCache’, ], ], Tip: You can register multiple cache application components. The component named cache is used by default by many cache- dependent classes (e.g. yiiwebUrlManager). Supported Cache Storage Yii supports a wide range of cache storage. The following is a summary: • yiicachingApcCache: uses PHP APC2 extension. This option can be considered as the fastest one when dealing with cache for a cent- ralized thick application (e.g. one server, no dedicated load balancers, etc.). • yiicachingDbCache: uses a database table to store cached data. To use this cache, you must create a table as specified in yiicaching DbCache::cacheTable. • yiicachingDummyCache: serves as a cache placeholder which does no real caching. The purpose of this component is to simplify the code that needs to check the availability of cache. For example, during development or if the server doesn’t have actual cache support, you may configure a cache component to use this cache. When an actual cache support is enabled, you can switch to use the corresponding cache component. In both cases, you may use the same code Yii::$app ->cache->get($key) to attempt retrieving data from the cache without worrying that Yii::$app->cache might be null. • yiicachingFileCache: uses standard files to store cached data. This is particular suitable to cache large chunk of data, such as page content. • yiicachingMemCache: uses PHP memcache3 and memcached4 ex- tensions. This option can be considered as the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load balancers, etc.) • yiiredisCache: implements a cache component based on Redis5 key-value store (redis version 2.6.12 or higher is required). 2 http://php.net/manual/en/book.apc.php 3 http://php.net/manual/en/book.memcache.php 4 http://php.net/manual/en/book.memcached.php 5 http://redis.io/
  • 338. 332 CHAPTER 10. CACHING • yiicachingWinCache: uses PHP WinCache6 (see also7) extension. • yiicachingXCache: uses PHP XCache8 extension. • Zend Data Cache9 as the underlying caching medium. Tip: You may use different cache storage in the same application. A common strategy is to use memory-based cache storage to store data that is small but constantly used (e.g. statistical data), and use file-based or database-based cache storage to store data that is big and less frequently used (e.g. page content). 10.2.2 Cache APIs All cache components have the same base class yiicachingCache and thus support the following APIs: • yiicachingCache::get(): retrieves a data item from cache with a specified key. A false value will be returned if the data item is not found in the cache or is expired/invalidated. • yiicachingCache::set(): stores a data item identified by a key in cache. • yiicachingCache::add(): stores a data item identified by a key in cache if the key is not found in the cache. • yiicachingCache::mget(): retrieves multiple data items from cache with the specified keys. • yiicachingCache::mset(): stores multiple data items in cache. Each item is identified by a key. • yiicachingCache::madd(): stores multiple data items in cache. Each item is identified by a key. If a key already exists in the cache, the data item will be skipped. • yiicachingCache::exists(): returns a value indicating whether the specified key is found in the cache. • yiicachingCache::delete(): removes a data item identified by a key from the cache. 6 http://iis.net/downloads/microsoft/wincache-extension 7 http://php.net/manual/en/book.wincache.php 8 http://xcache.lighttpd.net/ 9 http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_ component.htm
  • 339. 10.2. DATA CACHING 333 • yiicachingCache::flush(): removes all data items from the cache. Some cache storage, such as MemCache, APC, support retrieving multiple cached values in a batch mode, which may reduce the overhead involved in retrieving cached data. The APIs yiicachingCache::mget() and yii cachingCache::madd() are provided to exploit this feature. In case the underlying cache storage does not support this feature, it will be simulated. Because yiicachingCache implements ArrayAccess, a cache component can be used like an array. The followings are some examples: $cache[’var1’] = $value1; // equivalent to: $cache->set(’var1’, $value1); $value2 = $cache[’var2’]; // equivalent to: $value2 = $cache->get(’var2’); Cache Keys Each data item stored in cache is uniquely identified by a key. When you store a data item in cache, you have to specify a key for it. Later when you retrieve the data item from cache, you should provide the corresponding key. You may use a string or an arbitrary value as a cache key. When a key is not a string, it will be automatically serialized into a string. A common strategy of defining a cache key is to include all determining factors in terms of an array. For example, yiidbSchema uses the following key to cache schema information about a database table: [ __CLASS__, // schema class name $this->db->dsn, // DB connection data source name $this->db->username, // DB connection login user $name, // table name ]; As you can see, the key includes all necessary information needed to uniquely specify a database table. When the same cache storage is used by different applications, you should specify a unique cache key prefix for each application to avoid conflicts of cache keys. This can be done by configuring the yiicachingCache:: keyPrefix property. For example, in the application configuration you can write the following code: ’components’ => [ ’cache’ => [ ’class’ => ’yiicachingApcCache’, ’keyPrefix’ => ’myapp’, // a unique cache key prefix ], ], To ensure interoperability, only alphanumeric characters should be used.
  • 340. 334 CHAPTER 10. CACHING Cache Expiration A data item stored in a cache will remain there forever unless it is removed because of some caching policy enforcement (e.g. caching space is full and the oldest data are removed). To change this behavior, you can provide an expiration parameter when calling yiicachingCache::set() to store a data item. The parameter indicates for how many seconds the data item can remain valid in the cache. When you call yiicachingCache::get() to retrieve the data item, if it has passed the expiration time, the method will return false, indicating the data item is not found in the cache. For example, // keep the data in cache for at most 45 seconds $cache->set($key, $data, 45); sleep(50); $data = $cache->get($key); if ($data === false) { // $data is expired or is not found in the cache } Cache Dependencies Besides expiration setting, cached data item may also be invalidated by changes of the so-called cache dependencies. For example, yiicaching FileDependency represents the dependency of a file’s modification time. When this dependency changes, it means the corresponding file is modified. As a result, any outdated file content found in the cache should be invalidated and the yiicachingCache::get() call should return false. Cache dependencies are represented as objects of yiicachingDependency descendant classes. When you call yiicachingCache::set() to store a data item in the cache, you can pass along an associated cache dependency object. For example, // Create a dependency on the modification time of file example.txt. $dependency = new yiicachingFileDependency([’fileName’ => ’example.txt’]) ; // The data will expire in 30 seconds. // It may also be invalidated earlier if example.txt is modified. $cache->set($key, $data, 30, $dependency); // The cache will check if the data has expired. // It will also check if the associated dependency was changed. // It will return false if any of these conditions is met. $data = $cache->get($key); Below is a summary of the available cache dependencies: • yiicachingChainedDependency: the dependency is changed if any of the dependencies on the chain is changed.
  • 341. 10.2. DATA CACHING 335 • yiicachingDbDependency: the dependency is changed if the query result of the specified SQL statement is changed. • yiicachingExpressionDependency: the dependency is changed if the result of the specified PHP expression is changed. • yiicachingFileDependency: the dependency is changed if the file’s last modification time is changed. • yiicachingTagDependency: associates a cached data item with one or multiple tags. You may invalidate the cached data items with the specified tag(s) by calling yiicachingTagDependency::invalidate(). 10.2.3 Query Caching Query caching is a special caching feature built on top of data caching. It is provided to cache the result of database queries. Query caching requires a yiidbConnection and a valid cache applic- ation component. The basic usage of query caching is as follows, assuming $db is a yiidbConnection instance: $result = $db->cache(function ($db) { // the result of the SQL query will be served from the cache // if query caching is enabled and the query result is found in the cache return $db->createCommand(’SELECT * FROM customer WHERE id=1’)->queryOne (); }); Query caching can be used for DAO as well as ActiveRecord. Info: Some DBMS (e.g. MySQL10) also support query cach- ing on the DB server side. You may choose to use either query caching mechanism. The query caching described above has the advantage that you may specify flexible cache dependencies and are potentially more efficient. Configurations Query caching has three global configurable options through yiidbConnection: • yiidbConnection::enableQueryCache: whether to turn on or off query caching. It defaults to true. Note that to effectively turn on query caching, you also need to have a valid cache, as specified by yii dbConnection::queryCache. 10 http://dev.mysql.com/doc/refman/5.1/en/query-cache.html
  • 342. 336 CHAPTER 10. CACHING • yiidbConnection::queryCacheDuration: this represents the num- ber of seconds that a query result can remain valid in the cache. You can use 0 to indicate a query result should remain in the cache forever. This property is the default value used when yiidbConnection:: cache() is called without specifying a duration. • yiidbConnection::queryCache: this represents the ID of the cache application component. It defaults to ’cache’. Query caching is en- abled only if there is a valid cache application component. Usages You can use yiidbConnection::cache() if you have multiple SQL queries that need to take advantage of query caching. The usage is as follows, $duration = 60; // cache query results for 60 seconds. $dependency = ...; // optional dependency $result = $db->cache(function ($db) { // ... perform SQL queries here ... return $result; }, $duration, $dependency); Any SQL queries in the anonymous function will be cached for the specified duration with the specified dependency. If the result of a query is found valid in the cache, the query will be skipped and the result will be served from the cache instead. If you do not specify the $duration parameter, the value of yiidbConnection::queryCacheDuration will be used instead. Sometimes within cache(), you may want to disable query caching for some particular queries. You can use yiidbConnection::noCache() in this case. $result = $db->cache(function ($db) { // SQL queries that use query caching $db->noCache(function ($db) { // SQL queries that do not use query caching }); // ... return $result; }); If you just want to use query caching for a single query, you can call yiidb Command::cache() when building the command. For example,
  • 343. 10.3. FRAGMENT CACHING 337 // use query caching and set query cache duration to be 60 seconds $customer = $db->createCommand(’SELECT * FROM customer WHERE id=1’)->cache (60)->queryOne(); You can also use yiidbCommand::noCache() to disable query caching for a single command. For example, $result = $db->cache(function ($db) { // SQL queries that use query caching // do not use query caching for this command $customer = $db->createCommand(’SELECT * FROM customer WHERE id=1’)-> noCache()->queryOne(); // ... return $result; }); Limitations Query caching does not work with query results that contain resource hand- lers. For example, when using the BLOB column type in some DBMS, the query result will return a resource handler for the column data. Some caching storage has size limitation. For example, memcache limits the maximum size of each entry to be 1MB. Therefore, if the size of a query result exceeds this limit, the caching will fail. 10.3 Fragment Caching Fragment caching refers to caching a fragment of a Web page. For example, if a page displays a summary of yearly sale in a table, you can store this table in cache to eliminate the time needed to generate this table for each request. Fragment caching is built on top of data caching. To use fragment caching, use the following construct in a view: if ($this->beginCache($id)) { // ... generate content here ... $this->endCache(); } That is, enclose content generation logic in a pair of yiibaseView:: beginCache() and yiibaseView::endCache() calls. If the content is found in the cache, yiibaseView::beginCache() will render the cached content and return false, thus skip the content generation logic. Otherwise, your content generation logic will be called, and when yiibaseView::
  • 344. 338 CHAPTER 10. CACHING endCache() is called, the generated content will be captured and stored in the cache. Like data caching, a unique $id is needed to identify a content cache. 10.3.1 Caching Options You may specify additional options about fragment caching by passing the option array as the second parameter to the yiibaseView::beginCache() method. Behind the scene, this option array will be used to configure a yiiwidgetsFragmentCache widget which implements the actual fragment caching functionality. Duration Perhaps the most commonly used option of fragment caching is yiiwidgets FragmentCache::duration. It specifies for how many seconds the content can remain valid in a cache. The following code caches the content fragment for at most one hour: if ($this->beginCache($id, [’duration’ => 3600])) { // ... generate content here ... $this->endCache(); } If the option is not set, it will take the default value 0, which means the cached content will never expire. Dependencies Like data caching, content fragment being cached can also have dependen- cies. For example, the content of a post being displayed depends on whether or not the post is modified. To specify a dependency, set the yiiwidgetsFragmentCache::dependency option, which can be either an yiicachingDependency object or a config- uration array for creating a dependency object. The following code specifies that the fragment content depends on the change of the updated_at column value: $dependency = [ ’class’ => ’yiicachingDbDependency’, ’sql’ => ’SELECT MAX(updated_at) FROM post’, ]; if ($this->beginCache($id, [’dependency’ => $dependency])) { // ... generate content here ...
  • 345. 10.3. FRAGMENT CACHING 339 $this->endCache(); } Variations Content being cached may be variated according to some parameters. For example, for a Web application supporting multiple languages, the same piece of view code may generate the content in different languages. There- fore, you may want to make the cached content variated according to the current application language. To specify cache variations, set the yiiwidgetsFragmentCache::variations option, which should be an array of scalar values, each representing a par- ticular variation factor. For example, to make the cached content variated by the language, you may use the following code: if ($this->beginCache($id, [’variations’ => [Yii::$app->language]])) { // ... generate content here ... $this->endCache(); } Toggling Caching Sometimes you may want to enable fragment caching only when certain conditions are met. For example, for a page displaying a form, you only want to cache the form when it is initially requested (via GET request). Any subsequent display (via POST request) of the form should not be cached because the form may contain user input. To do so, you may set the yii widgetsFragmentCache::enabled option, like the following: if ($this->beginCache($id, [’enabled’ => Yii::$app->request->isGet])) { // ... generate content here ... $this->endCache(); } 10.3.2 Nested Caching Fragment caching can be nested. That is, a cached fragment can be enclosed within another fragment which is also cached. For example, the comments are cached in an inner fragment cache, and they are cached together with the post content in an outer fragment cache. The following code shows how two fragment caches can be nested: if ($this->beginCache($id1)) {
  • 346. 340 CHAPTER 10. CACHING // ...content generation logic... if ($this->beginCache($id2, $options2)) { // ...content generation logic... $this->endCache(); } // ...content generation logic... $this->endCache(); } Different caching options can be set for the nested caches. For example, the inner caches and the outer caches can use different cache duration values. Even when the data cached in the outer cache is invalidated, the inner cache may still provide the valid inner fragment. However, it is not true vice versa. If the outer cache is evaluated to be valid, it will continue to provide the same cached copy even after the content in the inner cache has been invalidated. Therefore, you must be careful in setting the durations or the dependencies of the nested caches, otherwise the outdated inner fragments may be kept in the outer fragment. 10.3.3 Dynamic Content When using fragment caching, you may encounter the situation where a large fragment of content is relatively static except at one or a few places. For example, a page header may display the main menu bar together with the name of the current user. Another problem is that the content being cached may contain PHP code that must be executed for every request (e.g. the code for registering an asset bundle). Both problems can be solved by the so-called dynamic content feature. A dynamic content means a fragment of output that should not be cached even if it is enclosed within a fragment cache. To make the content dynamic all the time, it has to be generated by executing some PHP code for every request, even if the enclosing content is being served from cache. You may call yiibaseView::renderDynamic() within a cached frag- ment to insert dynamic content at the desired place, like the following, if ($this->beginCache($id1)) { // ...content generation logic... echo $this->renderDynamic(’return Yii::$app->user->identity->name;’); // ...content generation logic... $this->endCache();
  • 347. 10.4. PAGE CACHING 341 } The yiibaseView::renderDynamic() method takes a piece of PHP code as its parameter. The return value of the PHP code is treated as the dynamic content. The same PHP code will be executed for every request, no matter the enclosing fragment is being served from cached or not. 10.4 Page Caching Page caching refers to caching the content of a whole page on the server side. Later when the same page is requested again, its content will be served from the cache instead of regenerating it from scratch. Page caching is supported by yiifiltersPageCache, an action filter. It can be used like the following in a controller class: public function behaviors() { return [ [ ’class’ => ’yiifiltersPageCache’, ’only’ => [’index’], ’duration’ => 60, ’variations’ => [ Yii::$app->language, ], ’dependency’ => [ ’class’ => ’yiicachingDbDependency’, ’sql’ => ’SELECT COUNT(*) FROM post’, ], ], ]; } The above code states that page caching should be used only for the index action; the page content should be cached for at most 60 seconds and should be variated by the current application language; and the cached page should be invalidated if the total number of posts is changed. As you can see, page caching is very similar to fragment caching. They both support options such as duration, dependencies, variations, and enabled. Their main difference is that page caching is implemented as an action filter while fragment caching a widget. You can use fragment caching as well as dynamic content together with page caching. 10.5 HTTP Caching Besides server-side caching that we have described in the previous sections, Web applications may also exploit client-side caching to save the time for
  • 348. 342 CHAPTER 10. CACHING generating and transmitting the same page content. To use client-side caching, you may configure yiifiltersHttpCache as a filter for controller actions whose rendering result may be cached on the client side. yiifiltersHttpCache only works for GET and HEAD requests. It can handle three kinds of cache-related HTTP headers for these requests: • yiifiltersHttpCache::lastModified • yiifiltersHttpCache::etagSeed • yiifiltersHttpCache::cacheControlHeader 10.5.1 Last-Modified Header The Last-Modified header uses a timestamp to indicate if the page has been modified since the client caches it. You may configure the yiifiltersHttpCache::lastModified prop- erty to enable sending the Last-Modified header. The property should be a PHP callable returning a UNIX timestamp about the page modification time. The signature of the PHP callable should be as follows, /** * @param Action $action the action object that is being handled currently * @param array $params the value of the "params" property * @return integer a UNIX timestamp representing the page modification time */ function ($action, $params) The following is an example of making use of the Last-Modified header: public function behaviors() { return [ [ ’class’ => ’yiifiltersHttpCache’, ’only’ => [’index’], ’lastModified’ => function ($action, $params) { $q = new yiidbQuery(); return $q->from(’post’)->max(’updated_at’); }, ], ]; } The above code states that HTTP caching should be enabled for the index action only. It should generate a Last-Modified HTTP header based on the last update time of posts. When a browser visits the index page for the first time, the page will be generated on the server and sent to the browser; If the browser visits the same page again and there is no post being modified during the period, the server will not re-generate the page, and the browser will use the cached version on the client side. As a result, server-side rendering and page content transmission are both skipped.
  • 349. 10.5. HTTP CACHING 343 10.5.2 ETag Header The “Entity Tag” (or ETag for short) header use a hash to represent the content of a page. If the page is changed, the hash will be changed as well. By comparing the hash kept on the client side with the hash generated on the server side, the cache may determine whether the page has been changed and should be re-transmitted. You may configure the yiifiltersHttpCache::etagSeed property to enable sending the ETag header. The property should be a PHP callable returning a seed for generating the ETag hash. The signature of the PHP callable should be as follows, /** * @param Action $action the action object that is being handled currently * @param array $params the value of the "params" property * @return string a string used as the seed for generating an ETag hash */ function ($action, $params) The following is an example of making use of the ETag header: public function behaviors() { return [ [ ’class’ => ’yiifiltersHttpCache’, ’only’ => [’view’], ’etagSeed’ => function ($action, $params) { $post = $this->findModel(Yii::$app->request->get(’id’)); return serialize([$post->title, $post->content]); }, ], ]; } The above code states that HTTP caching should be enabled for the view action only. It should generate an ETag HTTP header based on the title and content of the requested post. When a browser visits the view page for the first time, the page will be generated on the server and sent to the browser; If the browser visits the same page again and there is no change to the title and content of the post, the server will not re-generate the page, and the browser will use the cached version on the client side. As a result, server-side rendering and page content transmission are both skipped. ETags allow more complex and/or more precise caching strategies than Last-Modified headers. For instance, an ETag can be invalidated if the site has switched to another theme. Expensive ETag generation may defeat the purpose of using HttpCache and introduce unnecessary overhead, since they need to be re-evaluated on every request. Try to find a simple expression that invalidates the cache if the page content has been modified.
  • 350. 344 CHAPTER 10. CACHING Note: In compliance to RFC 723211, HttpCache will send out both ETag and Last-Modified headers if they are both configured. And if the client sends both of the If-None-Match header and the If- Modified-Since header, only the former will be respected. 10.5.3 Cache-Control Header The Cache-Control header specifies the general caching policy for pages. You may send it by configuring the yiifiltersHttpCache::cacheControlHeader property with the header value. By default, the following header will be sent: Cache-Control: public, max-age=3600 10.5.4 Session Cache Limiter When a page uses session, PHP will automatically send some cache-related HTTP headers as specified in the session.cache_limiter PHP INI setting. These headers may interfere or disable the caching that you want from HttpCache. To prevent this problem, by default HttpCache will disable sending these headers automatically. If you want to change this behavior, you should configure the yiifiltersHttpCache::sessionCacheLimiter property. The property can take a string value, including public, private, private_no_expire, and nocache. Please refer to the PHP manual about session_cache_limiter()12 for explanations about these values. 10.5.5 SEO Implications Search engine bots tend to respect cache headers. Since some crawlers have a limit on how many pages per domain they process within a certain time span, introducing caching headers may help indexing your site as they reduce the number of pages that need to be processed. 11 http://tools.ietf.org/html/rfc7232#section-2.4 12 http://www.php.net/manual/en/function.session-cache-limiter.php
  • 351. Chapter 11 RESTful Web Services 11.1 Quick Start Yii provides a whole set of tools to simplify the task of implementing RESTful Web Service APIs. In particular, Yii supports the following features about RESTful APIs: • Quick prototyping with support for common APIs for Active Record; • Response format (supporting JSON and XML by default) negotiation; • Customizable object serialization with support for selectable output fields; • Proper formatting of collection data and validation errors; • Support for HATEOAS1; • Efficient routing with proper HTTP verb check; • Built-in support for the OPTIONS and HEAD verbs; • Authentication and authorization; • Data caching and HTTP caching; • Rate limiting; In the following, we use an example to illustrate how you can build a set of RESTful APIs with some minimal coding effort. Assume you want to expose the user data via RESTful APIs. The user data are stored in the user DB table, and you have already created the yii dbActiveRecord class appmodelsUser to access the user data. 1 http://en.wikipedia.org/wiki/HATEOAS 345
  • 352. 346 CHAPTER 11. RESTFUL WEB SERVICES 11.1.1 Creating a Controller First, create a controller class appcontrollersUserController as follows, namespace appcontrollers; use yiirestActiveController; class UserController extends ActiveController { public $modelClass = ’appmodelsUser’; } The controller class extends from yiirestActiveController. By spe- cifying yiirestActiveController::modelClass as appmodelsUser, the controller knows what model can be used for fetching and manipulating data. 11.1.2 Configuring URL Rules Then, modify the configuration about the urlManager component in your ap- plication configuration: ’urlManager’ => [ ’enablePrettyUrl’ => true, ’enableStrictParsing’ => true, ’showScriptName’ => false, ’rules’ => [ [’class’ => ’yiirestUrlRule’, ’controller’ => ’user’], ], ] The above configuration mainly adds a URL rule for the user controller so that the user data can be accessed and manipulated with pretty URLs and meaningful HTTP verbs. 11.1.3 Trying it Out With the above minimal amount of effort, you have already finished your task of creating the RESTful APIs for accessing the user data. The APIs you have created include: • GET /users: list all users page by page; • HEAD /users: show the overview information of user listing; • POST /users: create a new user; • GET /users/123: return the details of the user 123; • HEAD /users/123: show the overview information of user 123;
  • 353. 11.1. QUICK START 347 • PATCH /users/123 and PUT /users/123: update the user 123; • DELETE /users/123: delete the user 123; • OPTIONS /users: show the supported verbs regarding endpoint /users; • OPTIONS /users/123: show the supported verbs regarding endpoint /users /123. Info: Yii will automatically pluralize controller names for use in endpoints. You may access your APIs with the curl command like the following, $ curl -i -H "Accept:application/json" "http://localhost/users" HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 X-Pagination-Per-Page: 20 Link: <http://localhost/users?page=1>; rel=self, <http://localhost/users?page=2>; rel=next, <http://localhost/users?page=50>; rel=last Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 [ { "id": 1, ... }, { "id": 2, ... }, ... ] Try changing the acceptable content type to be application/xml, and you will see the result is returned in XML format: $ curl -i -H "Accept:application/xml" "http://localhost/users" HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1
  • 354. 348 CHAPTER 11. RESTFUL WEB SERVICES X-Pagination-Per-Page: 20 Link: <http://localhost/users?page=1>; rel=self, <http://localhost/users?page=2>; rel=next, <http://localhost/users?page=50>; rel=last Transfer-Encoding: chunked Content-Type: application/xml <?xml version="1.0" encoding="UTF-8"?> <response> <item> <id>1</id> ... </item> <item> <id>2</id> ... </item> ... </response> Tip: You may also access your APIs via Web browser by entering the URL http://localhost/users. However, you may need some browser plugins to send specific request headers. As you can see, in the response headers, there are information about the total count, page count, etc. There are also links that allow you to navigate to other pages of data. For example, http://localhost/users?page=2 would give you the next page of the user data. Using the fields and expand parameters, you may also specify which fields should be included in the result. For example, the URL http://localhost/ users?fields=id,email will only return the id and email fields. Info: You may have noticed that the result of http://localhost/ users includes some sensitive fields, such as password_hash, auth_key . You certainly do not want these to appear in your API result. You can and should filter out these fields as described in the Response Formatting section. 11.1.4 Summary Using the Yii RESTful API framework, you implement an API endpoint in terms of a controller action, and you use a controller to organize the actions that implement the endpoints for a single type of resource. Resources are represented as data models which extend from the yii baseModel class. If you are working with databases (relational or NoSQL), it is recommended you use yiidbActiveRecord to represent resources. You may use yiirestUrlRule to simplify the routing to your API endpoints.
  • 355. 11.2. RESOURCES 349 While not required, it is recommended that you develop your RESTful APIs as a separate application, different from your Web front end and back end for easier maintenance. 11.2 Resources RESTful APIs are all about accessing and manipulating resources. You may view resources as models in the MVC paradigm. While there is no restriction in how to represent a resource, in Yii you usually would represent resources in terms of objects of yiibaseModel or its child classes (e.g. yiidbActiveRecord), for the following reasons: • yiibaseModel implements the yiibaseArrayable interface, which allows you to customize how you want to expose resource data through RESTful APIs. • yiibaseModel supports input validation, which is useful if your RESTful APIs need to support data input. • yiidbActiveRecord provides powerful DB data access and manip- ulation support, which makes it a perfect fit if your resource data is stored in databases. In this section, we will mainly describe how a resource class extending from yiibaseModel (or its child classes) can specify what data may be returned via RESTful APIs. If the resource class does not extend from yiibase Model, then all its public member variables will be returned. 11.2.1 Fields When including a resource in a RESTful API response, the resource needs to be serialized into a string. Yii breaks this process into two steps. First, the resource is converted into an array by yiirestSerializer. Second, the array is serialized into a string in a requested format (e.g. JSON, XML) by yiiwebResponseFormatterInterface. The first step is what you should mainly focus when developing a resource class. By overriding yiibaseModel::fields() and/or yiibaseModel:: extraFields(), you may specify what data, called fields, in the resource can be put into its array representation. The difference between these two methods is that the former specifies the default set of fields which should be included in the array representation, while the latter specifies additional fields which may be included in the array if an end user requests for them via the expand query parameter. For example,
  • 356. 350 CHAPTER 11. RESTFUL WEB SERVICES // returns all fields as declared in fields() http://localhost/users // only returns field id and email, provided they are declared in fields() http://localhost/users?fields=id,email // returns all fields in fields() and field profile if it is in extraFields () http://localhost/users?expand=profile // only returns field id, email and profile, provided they are in fields() and extraFields() http://localhost/users?fields=id,email&expand=profile Overriding fields() By default, yiibaseModel::fields() returns all model attributes as fields, while yiidbActiveRecord::fields() only returns the attributes which have been populated from DB. You can override fields() to add, remove, rename or redefine fields. The return value of fields() should be an array. The array keys are the field names, and the array values are the corresponding field definitions which can be either property/attribute names or anonymous functions returning the corresponding field values. In the special case when a field name is the same as its defining attribute name, you can omit the array key. For example, // explicitly list every field, best used when you want to make sure the changes // in your DB table or model attributes do not cause your field changes (to keep API backward compatibility). public function fields() { return [ // field name is the same as the attribute name ’id’, // field name is "email", the corresponding attribute name is " email_address" ’email’ => ’email_address’, // field name is "name", its value is defined by a PHP callback ’name’ => function () { return $this->first_name . ’ ’ . $this->last_name; }, ]; } // filter out some fields, best used when you want to inherit the parent implementation // and blacklist some sensitive fields. public function fields() { $fields = parent::fields();
  • 357. 11.2. RESOURCES 351 // remove fields that contain sensitive information unset($fields[’auth_key’], $fields[’password_hash’], $fields[’ password_reset_token’]); return $fields; } Warning: Because by default all attributes of a model will be included in the API result, you should examine your data to make sure they do not contain sensitive information. If there is such information, you should override fields() to filter them out. In the above example, we choose to filter out auth_key, password_hash and password_reset_token. Overriding extraFields() By default, yiibaseModel::extraFields() returns nothing, while yii dbActiveRecord::extraFields() returns the names of the relations that have been populated from DB. The return data format of extraFields() is the same as that of fields (). Usually, extraFields() is mainly used to specify fields whose values are objects. For example, given the following field declaration, public function fields() { return [’id’, ’email’]; } public function extraFields() { return [’profile’]; } the request with http://localhost/users?fields=id,email&expand=profile may return the following JSON data: [ { "id": 100, "email": "[email protected]", "profile": { "id": 100, "age": 30, } }, ... ]
  • 358. 352 CHAPTER 11. RESTFUL WEB SERVICES 11.2.2 Links HATEOAS2, an abbreviation for Hypermedia as the Engine of Application State, promotes that RESTful APIs should return information that allow clients to discover actions supported for the returned resources. The key of HATEOAS is to return a set of hyperlinks with relation information when resource data are served by the APIs. Your resource classes may support HATEOAS by implementing the yii webLinkable interface. The interface contains a single method yiiweb Linkable::getLinks() which should return a list of yiiwebLink. Typ- ically, you should return at least the self link representing the URL to the resource object itself. For example, use yiidbActiveRecord; use yiiwebLink; use yiiwebLinkable; use yiihelpersUrl; class User extends ActiveRecord implements Linkable { public function getLinks() { return [ Link::REL_SELF => Url::to([’user/view’, ’id’ => $this->id], true ), ]; } } When a User object is returned in a response, it will contain a _links element representing the links related to the user, for example, { "id": 100, "email": "[email protected]", // ... "_links" => [ "self": "https://example.com/users/100" ] } 11.2.3 Collections Resource objects can be grouped into collections. Each collection contains a list of resource objects of the same type. While collections can be represented as arrays, it is usually more desirable to represent them as data providers. This is because data providers support sorting and pagination of resources, which is a commonly needed feature 2 http://en.wikipedia.org/wiki/HATEOAS
  • 359. 11.3. CONTROLLERS 353 for RESTful APIs returning collections. For example, the following action returns a data provider about the post resources: namespace appcontrollers; use yiirestController; use yiidataActiveDataProvider; use appmodelsPost; class PostController extends Controller { public function actionIndex() { return new ActiveDataProvider([ ’query’ => Post::find(), ]); } } When a data provider is being sent in a RESTful API response, yiirest Serializer will take out the current page of resources and serialize them as an array of resource objects. Additionally, yiirestSerializer will also include the pagination information by the following HTTP headers: • X-Pagination-Total-Count: The total number of resources; • X-Pagination-Page-Count: The number of pages; • X-Pagination-Current-Page: The current page (1-based); • X-Pagination-Per-Page: The number of resources in each page; • Link: A set of navigational links allowing client to traverse the resources page by page. An example may be found in the Quick Start section. 11.3 Controllers After creating the resource classes and specifying how resource data should be formatted, the next thing to do is to create controller actions to expose the resources to end users through RESTful APIs. Yii provides two base controller classes to simplify your work of creating RESTful actions: yiirestController and yiirestActiveController. The difference between these two controllers is that the latter provides a default set of actions that are specifically designed to deal with resources represented as Active Record. So if you are using Active Record and are comfortable with the provided built-in actions, you may consider extending your controller classes from yiirestActiveController, which will allow you to create powerful RESTful APIs with minimal code.
  • 360. 354 CHAPTER 11. RESTFUL WEB SERVICES Both yiirestController and yiirestActiveController provide the following features, some of which will be described in detail in the next few sections: • HTTP method validation; • Content negotiation and Data formatting; • Authentication; • Rate limiting. yiirestActiveController in addition provides the following features: • A set of commonly needed actions: index, view, create, update, delete, options; • User authorization in regarding to the requested action and resource. 11.3.1 Creating Controller Classes When creating a new controller class, a convention in naming the control- ler class is to use the type name of the resource and use singular form. For example, to serve user information, the controller may be named as UserController. Creating a new action is similar to creating an action for a Web applica- tion. The only difference is that instead of rendering the result using a view by calling the render() method, for RESTful actions you directly return the data. The yiirestController::serializer and the yiiwebResponse will handle the conversion from the original data to the requested format. For example, public function actionView($id) { return User::findOne($id); } 11.3.2 Filters Most RESTful API features provided by yiirestController are imple- mented in terms of filters. In particular, the following filters will be executed in the order they are listed: • yiifiltersContentNegotiator: supports content negotiation, to be explained in the Response Formatting section; • yiifiltersVerbFilter: supports HTTP method validation;
  • 361. 11.3. CONTROLLERS 355 • yiifiltersAuthMethod: supports user authentication, to be ex- plained in the Authentication section; • yiifiltersRateLimiter: supports rate limiting, to be explained in the Rate Limiting section. These named filters are declared in the yiirestController::behaviors() method. You may override this method to configure individual filters, dis- able some of them, or add your own filters. For example, if you only want to use HTTP basic authentication, you may write the following code: use yiifiltersauthHttpBasicAuth; public function behaviors() { $behaviors = parent::behaviors(); $behaviors[’authenticator’] = [ ’class’ => HttpBasicAuth::className(), ]; return $behaviors; } 11.3.3 Extending ActiveController If your controller class extends from yiirestActiveController, you should set its yiirestActiveController::modelClass property to be the name of the resource class that you plan to serve through this controller. The class must extend from yiidbActiveRecord. Customizing Actions By default, yiirestActiveController provides the following actions: • yiirestIndexAction: list resources page by page; • yiirestViewAction: return the details of a specified resource; • yiirestCreateAction: create a new resource; • yiirestUpdateAction: update an existing resource; • yiirestDeleteAction: delete the specified resource; • yiirestOptionsAction: return the supported HTTP methods. All these actions are declared through the yiirestActiveController:: actions() method. You may configure these actions or disable some of them by overriding the actions() method, like shown the following,
  • 362. 356 CHAPTER 11. RESTFUL WEB SERVICES public function actions() { $actions = parent::actions(); // disable the "delete" and "create" actions unset($actions[’delete’], $actions[’create’]); // customize the data provider preparation with the "prepareDataProvider ()" method $actions[’index’][’prepareDataProvider’] = [$this, ’prepareDataProvider’ ]; return $actions; } public function prepareDataProvider() { // prepare and return a data provider for the "index" action } Please refer to the class references for individual action classes to learn what configuration options are available. Performing Access Check When exposing resources through RESTful APIs, you often need to check if the current user has the permission to access and manipulate the requested resource(s). With yiirestActiveController, this can be done by over- riding the yiirestActiveController::checkAccess() method like the following, /** * Checks the privilege of the current user. * * This method should be overridden to check whether the current user has the privilege * to run the specified action against the specified data model. * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. * * @param string $action the ID of the action to be executed * @param yiibaseModel $model the model to be accessed. If null, it means no specific model is being accessed. * @param array $params additional parameters * @throws ForbiddenHttpException if the user does not have access */ public function checkAccess($action, $model = null, $params = []) { // check if the user can access $action and $model // throw ForbiddenHttpException if access should be denied }
  • 363. 11.4. ROUTING 357 The checkAccess() method will be called by the default actions of yiirest ActiveController. If you create new actions and also want to perform access check, you should call this method explicitly in the new actions. Tip: You may implement checkAccess() by using the Role-Based Access Control (RBAC) component. 11.4 Routing With resource and controller classes ready, you can access the resources using the URL like http://localhost/index.php?r=user/create, similar to what you can do with normal Web applications. In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs. For example, a request POST /users would mean accessing the user/create action. This can be done easily by configuring the urlManager application component in the application configuration like the following: ’urlManager’ => [ ’enablePrettyUrl’ => true, ’enableStrictParsing’ => true, ’showScriptName’ => false, ’rules’ => [ [’class’ => ’yiirestUrlRule’, ’controller’ => ’user’], ], ] Compared to the URL management for Web applications, the main new thing above is the use of yiirestUrlRule for routing RESTful API re- quests. This special URL rule class will create a whole set of child URL rules to support routing and URL creation for the specified controller(s). For example, the above code is roughly equivalent to the following rules: [ ’PUT,PATCH users/<id>’ => ’user/update’, ’DELETE users/<id>’ => ’user/delete’, ’GET,HEAD users/<id>’ => ’user/view’, ’POST users’ => ’user/create’, ’GET,HEAD users’ => ’user/index’, ’users/<id>’ => ’user/options’, ’users’ => ’user/options’, ] And the following API endpoints are supported by this rule: • GET /users: list all users page by page; • HEAD /users: show the overview information of user listing; • POST /users: create a new user;
  • 364. 358 CHAPTER 11. RESTFUL WEB SERVICES • GET /users/123: return the details of the user 123; • HEAD /users/123: show the overview information of user 123; • PATCH /users/123 and PUT /users/123: update the user 123; • DELETE /users/123: delete the user 123; • OPTIONS /users: show the supported verbs regarding endpoint /users; • OPTIONS /users/123: show the supported verbs regarding endpoint /users /123. You may configure the only and except options to explicitly list which actions to support or which actions should be disabled, respectively. For example, [ ’class’ => ’yiirestUrlRule’, ’controller’ => ’user’, ’except’ => [’delete’, ’create’, ’update’], ], You may also configure patterns or extraPatterns to redefine existing patterns or add new patterns supported by this rule. For example, to support a new action search by the endpoint GET /users/search, configure the extraPatterns option as follows, [ ’class’ => ’yiirestUrlRule’, ’controller’ => ’user’, ’extraPatterns’ => [ ’GET search’ => ’search’, ], You may have noticed that the controller ID user appears in plural form as users in the endpoints. This is because yiirestUrlRule automatically pluralizes controller IDs for them to use in endpoints. You may disable this behavior by setting yiirestUrlRule::pluralize to be false, or if you want to use some special names you may configure the yiirestUrlRule ::controller property. 11.5 Response Formatting When handling a RESTful API request, an application usually takes the following steps that are related with response formatting: 1. Determine various factors that may affect the response format, such as media type, language, version, etc. This process is also known as content negotiation3. 3 http://en.wikipedia.org/wiki/Content_negotiation
  • 365. 11.5. RESPONSE FORMATTING 359 2. Convert resource objects into arrays, as described in the Resources section. This is done by yiirestSerializer. 3. Convert arrays into a string in the format as determined by the content negotiation step. This is done by yiiwebResponseFormatterInterface registered with the yiiwebResponse::formatters application com- ponent. 11.5.1 Content Negotiation Yii supports content negotiation via the yiifiltersContentNegotiator filter. The RESTful API base controller class yiirestController is equipped with this filter under the name of contentNegotiator. The filer provides response format negotiation as well as language negotiation. For example, if a RESTful API request contains the following header, Accept: application/json; q=1.0, */*; q=0.1 it will get a response in JSON format, like the following: $ curl -i -H "Accept: application/json; q=1.0, */*; q=0.1" "http://localhost /users" HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 X-Pagination-Per-Page: 20 Link: <http://localhost/users?page=1>; rel=self, <http://localhost/users?page=2>; rel=next, <http://localhost/users?page=50>; rel=last Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 [ { "id": 1, ... }, { "id": 2, ... }, ... ] Behind the scene, before a RESTful API controller action is executed, the yiifiltersContentNegotiator filter will check the Accept HTTP header in the request and set the yiiwebResponse::format to be ’json’. After
  • 366. 360 CHAPTER 11. RESTFUL WEB SERVICES the action is executed and returns the resulting resource object or collection, yiirestSerializer will convert the result into an array. And finally, yii webJsonResponseFormatter will serialize the array into a JSON string and include it in the response body. By default, RESTful APIs support both JSON and XML formats. To support a new format, you should configure the yiifiltersContentNegotiator ::formats property of the contentNegotiator filter like the following in your API controller classes: use yiiwebResponse; public function behaviors() { $behaviors = parent::behaviors(); $behaviors[’contentNegotiator’][’formats’][’text/html’] = Response:: FORMAT_HTML; return $behaviors; } The keys of the formats property are the supported MIME types, while the values are the corresponding response format names which must be suppor- ted in yiiwebResponse::formatters. 11.5.2 Data Serializing As we have described above, yiirestSerializer is the central piece re- sponsible for converting resource objects or collections into arrays. It recog- nizes objects implementing yiibaseArrayableInterface as well as yii dataDataProviderInterface. The former is mainly implemented by re- source objects, while the latter resource collections. You may configure the serializer by setting the yiirestController:: serializer property with a configuration array. For example, sometimes you may want to help simplify the client development work by including pagination information directly in the response body. To do so, configure the yiirestSerializer::collectionEnvelope property as follows: use yiirestActiveController; class UserController extends ActiveController { public $modelClass = ’appmodelsUser’; public $serializer = [ ’class’ => ’yiirestSerializer’, ’collectionEnvelope’ => ’items’, ]; } You may then get the following response for request http://localhost/users: HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT
  • 367. 11.6. AUTHENTICATION 361 Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 X-Pagination-Per-Page: 20 Link: <http://localhost/users?page=1>; rel=self, <http://localhost/users?page=2>; rel=next, <http://localhost/users?page=50>; rel=last Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 { "items": [ { "id": 1, ... }, { "id": 2, ... }, ... ], "_links": { "self": "http://localhost/users?page=1", "next": "http://localhost/users?page=2", "last": "http://localhost/users?page=50" }, "_meta": { "totalCount": 1000, "pageCount": 50, "currentPage": 1, "perPage": 20 } } 11.6 Authentication Unlike Web applications, RESTful APIs are usually stateless, which means sessions or cookies should not be used. Therefore, each request should come with some sort of authentication credentials because the user authentication status may not be maintained by sessions or cookies. A common practice is to send a secret access token with each request to authenticate the user. Since an access token can be used to uniquely identify and authenticate a user, API requests should always be sent via HTTPS to prevent from man-in-the-middle (MitM) attacks. There are different ways to send an access token:
  • 368. 362 CHAPTER 11. RESTFUL WEB SERVICES • HTTP Basic Auth4: the access token is sent as the username. This is should only be used when an access token can be safely stored on the API consumer side. For example, the API consumer is a program running on a server. • Query parameter: the access token is sent as a query parameter in the API URL, e.g., https://example.com/users?access-token=xxxxxxxx. Be- cause most Web servers will keep query parameters in server logs, this approach should be mainly used to serve JSONP requests which cannot use HTTP headers to send access tokens. • OAuth 25: the access token is obtained by the consumer from an authorization server and sent to the API server via HTTP Bearer Tokens6, according to the OAuth2 protocol. Yii supports all of the above authentication methods. You can also easily create new authentication methods. To enable authentication for your APIs, do the following steps: 1. Configure the yiiwebUser::enableSession property of the user ap- plication component to be false. 2. Specify which authentication methods you plan to use by configuring the authenticator behavior in your REST controller classes. 3. Implement yiiwebIdentityInterface::findIdentityByAccessToken() in your yiiwebUser::identityClass. Step 1 is not required but is recommended for RESTful APIs which should be stateless. When yiiwebUser::enableSession is false, the user authen- tication status will NOT be persisted across requests using sessions. Instead, authentication will be performed for every request, which is accomplished by Step 2 and 3. Tip: You may configure yiiwebUser::enableSession of the user application component in application configurations if you are developing RESTful APIs in terms of an application. If you develop RESTful APIs as a module, you may put the follow- ing line in the module’s init() method, like the following: ‘php public function init() { parent::init(); Yii::$app->user->enableSession = false; 4 http://en.wikipedia.org/wiki/Basic_access_authentication 5 http://oauth.net/2/ 6 http://tools.ietf.org/html/rfc6750
  • 369. 11.6. AUTHENTICATION 363 } ‘ For example, to use HTTP Basic Auth, you may configure authenticator as follows, use yiifiltersauthHttpBasicAuth; public function behaviors() { $behaviors = parent::behaviors(); $behaviors[’authenticator’] = [ ’class’ => HttpBasicAuth::className(), ]; return $behaviors; } If you want to support all three authentication methods explained above, you can use CompositeAuth like the following, use yiifiltersauthCompositeAuth; use yiifiltersauthHttpBasicAuth; use yiifiltersauthHttpBearerAuth; use yiifiltersauthQueryParamAuth; public function behaviors() { $behaviors = parent::behaviors(); $behaviors[’authenticator’] = [ ’class’ => CompositeAuth::className(), ’authMethods’ => [ HttpBasicAuth::className(), HttpBearerAuth::className(), QueryParamAuth::className(), ], ]; return $behaviors; } Each element in authMethods should be an auth method class name or a configuration array. Implementation of findIdentityByAccessToken() is application specific. For example, in simple scenarios when each user can only have one access token, you may store the access token in an access_token column in the user table. The method can then be readily implemented in the User class as follows, use yiidbActiveRecord; use yiiwebIdentityInterface; class User extends ActiveRecord implements IdentityInterface { public static function findIdentityByAccessToken($token, $type = null) { return static::findOne([’access_token’ => $token]); } }
  • 370. 364 CHAPTER 11. RESTFUL WEB SERVICES After authentication is enabled as described above, for every API request, the requested controller will try to authenticate the user in its beforeAction() step. If authentication succeeds, the controller will perform other checks (such as rate limiting, authorization) and then run the action. The authenticated user identity information can be retrieved via Yii::$app->user->identity. If authentication fails, a response with HTTP status 401 will be sent back together with other appropriate headers (such as a WWW-Authenticate header for HTTP Basic Auth). 11.6.1 Authorization After a user is authenticated, you probably want to check if he or she has the permission to perform the requested action for the requested resource. This process is called authorization which is covered in detail in the Authorization section. If your controllers extend from yiirestActiveController, you may override the yiirestController::checkAccess() method to perform au- thorization check. The method will be called by the built-in actions provided by yiirestActiveController. 11.7 Rate Limiting To prevent abuse, you should consider adding rate limiting to your APIs. For example, you may want to limit the API usage of each user to be at most 100 API calls within a period of 10 minutes. If too many requests are received from a user within the stated period of the time, a response with status code 429 (meaning “Too Many Requests”) should be returned. To enable rate limiting, the yiiwebUser::identityClass should im- plement yiifiltersRateLimitInterface. This interface requires imple- mentation of three methods: • getRateLimit(): returns the maximum number of allowed requests and the time period (e.g., [100, 600] means there can be at most 100 API calls within 600 seconds). • loadAllowance(): returns the number of remaining requests allowed and the corresponding UNIX timestamp when the rate limit was last checked. • saveAllowance(): saves both the number of remaining requests allowed and the current UNIX timestamp. You may want to use two columns in the user table to record the allow- ance and timestamp information. With those defined, then loadAllowance()
  • 371. 11.8. VERSIONING 365 and saveAllowance() can be implemented to read and save the values of the two columns corresponding to the current authenticated user. To improve performance, you may also consider storing these pieces of information in a cache or NoSQL storage. Once the identity class implements the required interface, Yii will auto- matically use yiifiltersRateLimiter configured as an action filter for yiirestController to perform rate limiting check. The rate limiter will throw a yiiwebTooManyRequestsHttpException when the rate limit is exceeded. You may configure the rate limiter as follows in your REST controller classes: public function behaviors() { $behaviors = parent::behaviors(); $behaviors[’rateLimiter’][’enableRateLimitHeaders’] = false; return $behaviors; } When rate limiting is enabled, by default every response will be sent with the following HTTP headers containing the current rate limiting information: • X-Rate-Limit-Limit, the maximum number of requests allowed with a time period • X-Rate-Limit-Remaining, the number of remaining requests in the current time period • X-Rate-Limit-Reset, the number of seconds to wait in order to get the maximum number of allowed requests You may disable these headers by configuring yiifiltersRateLimiter:: enableRateLimitHeaders to be false, as shown in the above code example. 11.8 Versioning A good API is versioned: changes and new features are implemented in new versions of the API instead of continually altering just one version. Unlike Web applications, with which you have full control of both the client-side and server-side code, APIs are meant to be used by clients beyond your control. For this reason, backward compatibility (BC) of the APIs should be maintained whenever possible. If a change that may break BC is necessary, you should introduce it in new version of the API, and bump up the version number. Existing clients can continue to use the old, working version of the API; and new or upgraded clients can get the new functionality in the new API version.
  • 372. 366 CHAPTER 11. RESTFUL WEB SERVICES Tip: Refer to Semantic Versioning7 for more information on designing API version numbers. One common way to implement API versioning is to embed the version number in the API URLs. For example, http://example.com/v1/users stands for the /users endpoint of API version 1. Another method of API versioning, which has gained momentum re- cently, is to put the version number in the HTTP request headers. This is typically done through the Accept header: // via a parameter Accept: application/json; version=v1 // via a vendor content type Accept: application/vnd.company.myapp-v1+json Both methods have their pros and cons, and there are a lot of debates about each approach. Below you’ll see a practical strategy for API versioning that is a mix of these two methods: • Put each major version of API implementation in a separate module whose ID is the major version number (e.g. v1, v2). Naturally, the API URLs will contain major version numbers. • Within each major version (and thus within the corresponding mod- ule), use the Accept HTTP request header to determine the minor version number and write conditional code to respond to the minor versions accordingly. For each module serving a major version, the module should include the re- source and controller classes serving that specific version. To better separate code responsibility, you may keep a common set of base resource and con- troller classes, and subclass them in each individual version module. Within the subclasses, implement the concrete code such as Model::fields(). Your code may be organized like the following: api/ common/ controllers/ UserController.php PostController.php models/ User.php Post.php modules/ v1/ controllers/ UserController.php PostController.php 7 http://semver.org/
  • 373. 11.8. VERSIONING 367 models/ User.php Post.php v2/ controllers/ UserController.php PostController.php models/ User.php Post.php Your application configuration would look like: return [ ’modules’ => [ ’v1’ => [ ’basePath’ => ’@app/modules/v1’, ], ’v2’ => [ ’basePath’ => ’@app/modules/v2’, ], ], ’components’ => [ ’urlManager’ => [ ’enablePrettyUrl’ => true, ’enableStrictParsing’ => true, ’showScriptName’ => false, ’rules’ => [ [’class’ => ’yiirestUrlRule’, ’controller’ => [’v1/user’, ’v1/post’]], [’class’ => ’yiirestUrlRule’, ’controller’ => [’v2/user’, ’v2/post’]], ], ], ], ]; As a result of the above code, http://example.com/v1/users will return the list of users in version 1, while http://example.com/v2/users will return version 2 users. Thanks to modules, the code for different major versions can be well isolated. But modules make it still possible to reuse code across the modules via common base classes and other shared resources. To deal with minor version numbers, you may take advantage of the con- tent negotiation feature provided by the yiifiltersContentNegotiator behavior. The contentNegotiator behavior will set the yiiwebResponse:: acceptParams property when it determines which content type to support. For example, if a request is sent with the HTTP header Accept: application /json; version=v1, after content negotiation, yiiwebResponse::acceptParams will contain the value [’version’ => ’v1’]. Based on the version information in acceptParams, you may write condi- tional code in places such as actions, resource classes, serializers, etc. to
  • 374. 368 CHAPTER 11. RESTFUL WEB SERVICES provide the appropriate functionality. Since minor versions by definition require maintaining backward com- patibility, hopefully there would not be many version checks in your code. Otherwise, chances are that you may need to create a new major version. 11.9 Error Handling When handling a RESTful API request, if there is an error in the user request or if something unexpected happens on the server, you may simply throw an exception to notify the user that something went wrong. If you can identify the cause of the error (e.g., the requested resource does not exist), you should consider throwing an exception along with a proper HTTP status code (e.g., yiiwebNotFoundHttpException represents a 404 status code). Yii will send the response along with the corresponding HTTP status code and text. Yii will also include the serialized representation of the exception in the response body. For example: HTTP/1.1 404 Not Found Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 { "name": "Not Found Exception", "message": "The requested resource was not found.", "code": 0, "status": 404 } The following list summarizes the HTTP status code that are used by the Yii REST framework: • 200: OK. Everything worked as expected. • 201: A resource was successfully created in response to a POST request. The Location header contains the URL pointing to the newly created resource. • 204: The request was handled successfully and the response contains no body content (like a DELETE request). • 304: The resource was not modified. You can use the cached version. • 400: Bad request. This could be caused by various actions by the user, such as providing invalid JSON data in the request body, providing invalid action parameters, etc. • 401: Authentication failed.
  • 375. 11.9. ERROR HANDLING 369 • 403: The authenticated user is not allowed to access the specified API endpoint. • 404: The requested resource does not exist. • 405: Method not allowed. Please check the Allow header for the allowed HTTP methods. • 415: Unsupported media type. The requested content type or version number is invalid. • 422: Data validation failed (in response to a POST request, for example). Please check the response body for detailed error messages. • 429: Too many requests. The request was rejected due to rate limiting. • 500: Internal server error. This could be caused by internal program errors. 11.9.1 Customizing Error Response Sometimes you may want to customize the default error response format. For example, instead of relying on using different HTTP statuses to indicate different errors, you would like to always use 200 as HTTP status and enclose the actual HTTP status code as part of the JSON structure in the response, like shown in the following, HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 { "success": false, "data": { "name": "Not Found Exception", "message": "The requested resource was not found.", "code": 0, "status": 404 } } To achieve this goal, you can respond to the beforeSend event of the response component in the application configuration: return [ // ... ’components’ => [ ’response’ => [ ’class’ => ’yiiwebResponse’, ’on beforeSend’ => function ($event) {
  • 376. 370 CHAPTER 11. RESTFUL WEB SERVICES $response = $event->sender; if ($response->data !== null && !empty(Yii::$app->request-> get[’suppress_response_code’])) { $response->data = [ ’success’ => $response->isSuccessful, ’data’ => $response->data, ]; $response->statusCode = 200; } }, ], ], ]; The above code will reformat the response (for both successful and failed responses) as explained when suppress_response_code is passed as a GET para- meter.
  • 377. Chapter 12 Development Tools 12.1 Debug toolbar and debugger Note: This section is under development. Yii2 includes a handy toolbar, and built-in debugger, for faster development and debugging of your applications. The toolbar displays information about the currently opened page, while the debugger can be used to analyze data you’ve previously collected (i.e., to confirm the values of variables). Out of the box these tools allow you to: • Quickly get the framework version, PHP version, response status, cur- rent controller and action, performance info and more via toolbar • Browse the application and PHP configuration • View the request data, request and response headers, session data, and environment variables • See, search, and filter the logs • View any profiling results • View the database queries executed by the page • View the emails sent by the application All of this information will be available per request, allowing you to revisit the information for past requests as well. 12.1.1 Installing and configuring To enable these features, add these lines to your configuration file to enable the debug module: 371
  • 378. 372 CHAPTER 12. DEVELOPMENT TOOLS ’bootstrap’ => [’debug’], ’modules’ => [ ’debug’ => ’yiidebugModule’, ] By default, the debug module only works when browsing the website from localhost. If you want to use it on a remote (staging) server, add the para- meter allowedIPs to the configuration to whitelist your IP: ’bootstrap’ => [’debug’], ’modules’ => [ ’debug’ => [ ’class’ => ’yiidebugModule’, ’allowedIPs’ => [’1.2.3.4’, ’127.0.0.1’, ’::1’] ] ] If you are using enableStrictParsing URL manager option, add the following to your rules: ’urlManager’ => [ ’enableStrictParsing’ => true, ’rules’ => [ // ... ’debug/<controller>/<action>’ => ’debug/<controller>/<action>’, ], ], Note: the debugger stores information about each request in the @runtime/debug directory. If you have problems using The debug- ger such as weird error messages when using it or the toolbar not showing up or not showing any requests, check whether the web server has enough permissions to access this directory and the files located inside. Extra configuration for logging and profiling Logging and profiling are simple but powerful tools that may help you to understand the execution flow of both the framework and the application. These tools are useful for development and production environments alike. While in a production environment, you should log only significantly important messages manually, as described in logging guide section. It hurts performance too much to continue to log all messages in production. In a development environment, the more logging the better, and it’s especially useful to record the execution trace. In order to see the trace messages that will help you to understand what happens under the hood of the framework, you need to set the trace level in the configuration file:
  • 379. 12.1. DEBUG TOOLBAR AND DEBUGGER 373 return [ // ... ’components’ => [ ’log’ => [ ’traceLevel’ => YII_DEBUG ? 3 : 0, // <-- here By default, the trace level is automatically set to 3 if Yii is running in debug mode, as determined by the presence of the following line in your index.php file: defined(’YII_DEBUG’) or define(’YII_DEBUG’, true); Note: Make sure to disable debug mode in production environ- ments since it may have a significant and adverse performance effect. Further, the debug mode may expose sensitive informa- tion to end users. 12.1.2 Creating your own panels Both the toolbar and debugger are highly configurable and customizable. To do so, you can create your own panels that collect and display the specific data you want. Below we’ll describe the process of creating a simple custom panel that: • Collects the views rendered during a request • Shows the number of views rendered in the toolbar • Allows you to check the view names in the debugger The assumption is that you’re using the basic application template. First we need to implement the Panel class in panels/ViewsPanel.php: <?php namespace apppanels; use yiibaseEvent; use yiibaseView; use yiibaseViewEvent; use yiidebugPanel; class ViewsPanel extends Panel { private $_viewFiles = []; public function init() { parent::init(); Event::on(View::className(), View::EVENT_BEFORE_RENDER, function ( ViewEvent $event) {
  • 380. 374 CHAPTER 12. DEVELOPMENT TOOLS $this->_viewFiles[] = $event->sender->getViewFile(); }); } /** * @inheritdoc */ public function getName() { return ’Views’; } /** * @inheritdoc */ public function getSummary() { $url = $this->getUrl(); $count = count($this->data); return "<div class="yii-debug-toolbar-block"><a href="$url"> Views <span class="label">$count</span></a></div>"; } /** * @inheritdoc */ public function getDetail() { return ’<ol><li>’ . implode(’<li>’, $this->data) . ’</ol>’; } /** * @inheritdoc */ public function save() { return $this->_viewFiles; } } The workflow for the code above is: 1. init is executed before any controller action is run. This method is the best place to attach handlers that will collect data during the controller action’s execution. 2. save is called after controller action is executed. The data returned by this method will be stored in a data file. If nothing is returned by this method, the panel won’t be rendered. 3. The data from the data file is loaded into $this->data. For the tool- bar, this will always represent the latest data, For the debugger, this
  • 381. 12.2. THE GII CODE GENERATION TOOL 375 property may be set to be read from any previous data file as well. 4. The toolbar takes its contents from getSummary. There, we’re showing the number of view files rendered. The debugger uses getDetail for the same purpose. Now it’s time to tell the debugger to use the new panel. In config/web.php, the debug configuration is modified to: if (YII_ENV_DEV) { // configuration adjustments for ’dev’ environment $config[’bootstrap’][] = ’debug’; $config[’modules’][’debug’] = [ ’class’ => ’yiidebugModule’, ’panels’ => [ ’views’ => [’class’ => ’apppanelsViewsPanel’], ], ]; // ... That’s it. Now we have another useful panel without writing much code. 12.2 The Gii code generation tool Note: This section is under development. Yii includes a handy tool, named Gii, that provides rapid prototyping by generating commonly used code snippets as well as complete CRUD control- lers. Gii provides a Web-based interface for you to interactively generate the code you want. It also provides a command line interface for people who prefer to work with their console windows most of the time. 12.2.1 Installing and configuring Gii is an official Yii extension. The preferred way to install this extension is through composer1. You can either run this command: php composer.phar require --prefer-dist yiisoft/yii2-gii "*" Or you can add this code to the require section of your composer.json file: "yiisoft/yii2-gii": "*" Once the Gii extension has been installed, you enable it by adding these lines to your application configuration file: 1 http://getcomposer.org/download/
  • 382. 376 CHAPTER 12. DEVELOPMENT TOOLS return [ ’bootstrap’ => [’gii’], ’modules’ => [ ’gii’ => ’yiigiiModule’, // ... ], // ... ]; You can then access Gii through the following URL: http://localhost/path/to/index.php?r=gii If you have enabled pretty URLs, you may use the following URL: http://localhost/path/to/index.php/gii Note: if you are accessing gii from an IP address other than localhost, access will be denied by default. To circumvent that default, add the allowed IP addresses to the configuration: ’gii’ => [ ’class’ => ’yiigiiModule’, ’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’ 192.168.178.20’] // adjust this to your needs ], If you have configured Gii similarly in your console application configuration, you may also access Gii through command window like the following: # change path to your application’s base path cd path/to/AppBasePath # show help information about Gii yii help gii # show help information about the model generator in Gii yii help gii/model # generate City model from city table yii gii/model --tableName=city --modelClass=City Basic application In basic application template configuration structure is a bit different so Gii should be configured in config/web.php: // ... if (YII_ENV_DEV) { // configuration adjustments for ’dev’ environment $config[’bootstrap’][] = ’debug’; $config[’modules’][’debug’] = ’yiidebugModule’;
  • 383. 12.2. THE GII CODE GENERATION TOOL 377 $config[’bootstrap’][] = ’gii’; $config[’modules’][’gii’] = ’yiigiiModule’; // <--- here } So in order to adjust IP address you need to do it like the following: if (YII_ENV_DEV) { // configuration adjustments for ’dev’ environment $config[’bootstrap’][] = ’debug’; $config[’modules’][’debug’] = ’yiidebugModule’; $config[’bootstrap’][] = ’gii’; $config[’modules’][’gii’] = [ ’class’ => ’yiigiiModule’, ’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’192.168.178.20’ ], ]; } 12.2.2 How to use it When you open Gii you first see the entry page that lets you choose a gen- erator. By default there are the following generators available: • Model Generator - This generator generates an ActiveRecord class for the specified database table. • CRUD Generator - This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) operations for the specified data model.
  • 384. 378 CHAPTER 12. DEVELOPMENT TOOLS • Controller Generator - This generator helps you to quickly gener- ate a new controller class, one or several controller actions and their corresponding views. • Form Generator - This generator generates a view script file that displays a form to collect input for the specified model class. • Module Generator - This generator helps you to generate the skel- eton code needed by a Yii module. • Extension Generator - This generator helps you to generate the files needed by a Yii extension. After choosing a generator by clicking on the “Start” button you will see a form that allows you to configure the parameters of the generator. Fill out the form according to your needs and press the “Preview” button to get a preview of the code that gii is about to generated. Depending on the generator you chose and whether the files already existed or not, you will get an output similar to what you see in the following picture: Clicking on the file name you can view a preview of the code that will be generated for that file. When the file already exists, gii also provides a diff view that shows what is different between the code that exists and the one that will be generated. In this case you can also choose which files should be overridden and which not. Tip: When using the Model Generator to update models after database change, you can copy the code from gii preview and merge the changes with your own code. You can use IDE features like PHPStorms compare with clipboard2 for this, which allows you to merge in relevant changes and leave out others that may revert your own code. 2 http://www.jetbrains.com/phpstorm/webhelp/comparing-files.html
  • 385. 12.2. THE GII CODE GENERATION TOOL 379 After you have reviewed the code and selected the files to be generated you can click the “Generate” button to create the files. If all went fine you are done. When you see errors that gii is not able to generate the files you have to adjust directory permissions so that your webserver is able to write to the directories and create the files. Note: The code generated by gii is only a template that has to be adjusted to your needs. It is there to help you create new things quickly but it is not something that creates ready to use code. We often see people using the models generated by gii without change and just extend them to adjust some parts of it. This is not how it is meant to be used. Code generated by gii may be incomplete or incorrect and has to be changed to fit your needs before you can use it. 12.2.3 Creating your own templates Every generator has a form field Code Template that lets you choose a template to use for code generation. By default gii only provides one template default but you can create your own templates that are adjusted to your needs. If you open a folder @appvendoryiisoftyii2-giigenerators, you’ll see six folders of generators. ‘ + controller - crud + default • extension • form • model • module This is name generator. If you open any of these folders, you can see the folder ‘default‘. This folder is name of the template. Copy folder @appvendoryiisoftyii2-giigeneratorscruddefault to another location, for example @appmyTemplatescrud. Now open this folder and modify any template to fit your desires, for example, add errorSummary in views_form .php: <?php //... <div class="<?= Inflector::camel2id(StringHelper::basename($generator-> modelClass)) ?>-form"> <?= "<?php " ?>$form = ActiveForm::begin(); ?> <?= "<?=" ?> $form->errorSummary($model) ?> <!-- ADDED HERE --> <?php foreach ($safeAttributes as $attribute) {
  • 386. 380 CHAPTER 12. DEVELOPMENT TOOLS echo " <?= " . $generator->generateActiveField($attribute) . " ?>nn"; } ?> //... Now you need to tell GII about our template.The setting is made in the config file: // config/web.php for basic app // ... if (YII_ENV_DEV) { $config[’modules’][’gii’] = [ ’class’ => ’yiigiiModule’, ’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’192.168.178.20’ ], ’generators’ => [ //here ’crud’ => [ //name generator ’class’ => ’yiigiigeneratorscrudGenerator’, //class generator ’templates’ => [ //setting for out templates ’myCrud’ => ’@app/myTemplates/crud/default’, //name template => path to template ] ] ], ]; } Open the CRUD generator and you will see that in the field Code Template of form appeared own template . 12.2.4 Creating your own generators Open the folder of any generator and you will see two files form.php and Generator.php. One is the form, the second is the class generator. For create your own generator, you need to create or override these classes in any folder. Again as in the previous paragraph customize configuration: //config/web.php for basic app //.. if (YII_ENV_DEV) { $config[’modules’][’gii’] = [ ’class’ => ’yiigiiModule’, ’allowedIPs’ => [’127.0.0.1’, ’::1’, ’192.168.0.*’, ’192.168.178.20’ ], ’generators’ => [ ’myCrud’ => [ ’class’ => ’appmyTemplatescrudGenerator’, ’templates’ => [ ’my’ => ’@app/myTemplates/crud/default’, ] ] ], ];
  • 387. 12.2. THE GII CODE GENERATION TOOL 381 } // @app/myTemplates/crud/Generator.php <?php namespace appmyTemplatescrud; class Generator extends yiigiiGenerator { public function getName() { return ’MY CRUD Generator’; } public function getDescription() { return ’My crud generator. The same as a native, but he is mine...’; } // ... } Open Gii Module and you will see a new generator appears in it.
  • 388. 382 CHAPTER 12. DEVELOPMENT TOOLS Error: not existing file: tool-api-doc.md
  • 389. Chapter 13 Testing 13.1 Testing Testing is an important part of software development. Whether we are aware of it or not, we conduct testing continuously. For example, when we write a class in PHP, we may debug it step by step or simply use echo or die statements to verify that implementation works according to our initial plan. In case of web application we’re entering some test data in forms to ensure the page interacts with us as expected. The testing process could be automated so that each time when we need to verify something, we just need to call up the code that do it for us. The code that verifies that result matches what we’ve planned is called test and the process of its creation and further execution is known as automated testing, which is the main topic of testing chapters. 13.1.1 Developing with tests Test-Driven Development (TDD) and Behavior-Driven Development (BDD) are approaches of developing software by describing behavior of a piece of code or the whole feature as a set of scenarios or tests before writing actual code and only then creating the implementation that allows these tests to pass verifying that intended behavior is achieved. The process of developing a feature is the following: • Create a new test that describes a feature to be implemented. • Run new test and make sure it fails. It is expected since there’s no implementation yet. • Write simple code to make the new test pass. • Run all tests and make sure they all pass. • Improve code and make sure tests are still OK. 383
  • 390. 384 CHAPTER 13. TESTING After it’s done the process is repeated again for another feature or improve- ment. If existing feature is to be changed, tests should be changed as well. Tip: If you feel that you are loosing time doing a lot of small and simple iterations try covering more by your test scenario so you do more before executing tests again. If you’re debugging too much try doing the opposite. The reason to create tests before doing any implemenation is that it allows you to focus on what do we want to achieve and fully dive into “how to do it” afterwards. Usually it leads to better abstractions and easier test maintenance when it comes to feature adjustments in for of less coupled components. So to sum up pros of such approach are the following: • Keeps you focused on one thing at a time so both planning and imple- mentation are getting better. • Results in test-covering more features in greater detail i.e. if tests are OK most probably nothing’s broken. In the long term it usually gives you a good time-saving effect. Tip: If you want to know more about the principles for gathering software requirements and modeling the subject matter it’s good to learn Domain Driven Development (DDD)1. 13.1.2 When and how to test While test first approach described above makes sense for long term and relatively complex projects it could be overkill for simpler ones. There are some indicators of when it’s appropriate: • Project is already large and complex. • Project requirements are starting to get complex. Project grows con- stantly. • Project is meant to be long term. • The cost of the failure is too high. There’s nothing wrong in creating tests covering behavior of existing imple- mentation. • Project is a legacy one to be gradually renewed. 1 https://en.wikipedia.org/wiki/Domain-driven_design
  • 391. 13.2. TESTING ENVIRONMENT SETUP 385 • You’ve got a project to work on and it has no tests. In some cases any form of automated testing could be overkill: • Project is simple and isn’t getting any complex. • It’s one-time project that’s going to be expired. Still if you have time it’s good to automate testing in these cases as well. 13.1.3 Further reading • Test Driven Development: By Example / Kent Beck. ISBN: 0321146530. 13.2 Testing environment setup Note: This section is under development. Yii2 has officially maintained integration with Codeception2 testing framework that allows you to create the following test types: • Unit testing - verifies that a single unit of code is working as expected; • Functional testing - verifies scenarios from a user’s perspective via browser emulation; • Acceptance testing - verifies scenarios from a user’s perspective in a browser. Yii provides ready to use test sets for all three test types in both yii2-basic3 and yii2-advanced4 application templates. In order to run tests you need to install Codeception5. A good way to install it is the following: composer global require "codeception/codeception=2.0.*" composer global require "codeception/specify=*" composer global require "codeception/verify=*" If you’ve never used Composer for global packages before, run composer global status. It should output: Changed current directory to <directory> Then add <directory>/vendor/bin to you PATH environment variable. Now we’re able to use codecept from command line globally. 2 https://github.com/Codeception/Codeception 3 https://github.com/yiisoft/yii2/tree/master/apps/basic 4 https://github.com/yiisoft/yii2/tree/master/apps/advanced 5 https://github.com/Codeception/Codeception
  • 392. 386 CHAPTER 13. TESTING 13.3 Unit Tests Note: This section is under development. A unit test verifies that a single unit of code is working as expected. In object-oriented programming, the most basic code unit is a class. A unit test thus mainly needs to verify that each of the class interface methods works properly. That is, given different input parameters, the test verifies the method returns expected results. Unit tests are usually developed by people who write the classes being tested. Unit testing in Yii is built on top of PHPUnit and, optionally, Codecep- tion so it’s recommended to go through their docs: • PHPUnit docs starting from chapter 26. • Codeception Unit Tests7. 13.3.1 Running basic and advanced template unit tests Please refer to instructions provided in apps/advanced/tests/README.md and apps /basic/tests/README.md. 13.4 Functional Tests Note: This section is under development. • http://codeception.com/docs/05-FunctionalTests 13.4.1 Running basic and advanced template functional tests Please refer to instructions provided in apps/advanced/tests/README.md and apps /basic/tests/README.md. 13.5 Acceptance Tests Note: This section is under development. • http://codeception.com/docs/04-AcceptanceTests 13.5.1 Running basic and advanced template acceptance tests Please refer to instructions provided in apps/advanced/tests/README.md and apps /basic/tests/README.md. 6 http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html 7 http://codeception.com/docs/06-UnitTests
  • 393. 13.6. FIXTURES 387 13.6 Fixtures Note: This section is under development. Fixtures are important part of testing. Their main purpose is to set up the environment in a fixed/known state so that your tests are repeatable and run in an expected way. Yii provides a fixture framework that allows you to define your fixtures precisely and use them easily. A key concept in the Yii fixture framework is the so-called fixture objects. A fixture object represents a particular aspect of a test environment and is an instance of yiitestFixture or its child class. For example, you may use UserFixture to make sure the user DB table contains a fixed set of data. You load one or multiple fixture objects before running a test and unload them when finishing. A fixture may depend on other fixtures, specified via its yiitestFixture ::depends property. When a fixture is being loaded, the fixtures it depends on will be automatically loaded BEFORE the fixture; and when the fixture is being unloaded, the dependent fixtures will be unloaded AFTER the fixture. 13.6.1 Defining a Fixture To define a fixture, create a new class by extending yiitestFixture or yiitestActiveFixture. The former is best suited for general purpose fixtures, while the latter has enhanced features specifically designed to work with database and ActiveRecord. The following code defines a fixture about the User ActiveRecord and the corresponding user table. <?php namespace apptestsfixtures; use yiitestActiveFixture; class UserFixture extends ActiveFixture { public $modelClass = ’appmodelsUser’; } Tip: Each ActiveFixture is about preparing a DB table for test- ing purpose. You may specify the table by setting either the yii testActiveFixture::tableName property or the yiitestActiveFixture ::modelClass property. If the latter, the table name will be taken from the ActiveRecord class specified by modelClass. The fixture data for an ActiveFixture fixture is usually provided in a file located at FixturePath/data/TableName.php, where FixturePath stands for the directory containing the fixture class file, and TableName is the name of the
  • 394. 388 CHAPTER 13. TESTING table associated with the fixture. In the example above, the file should be @app/tests/fixtures/data/user.php. The data file should return an array of data rows to be inserted into the user table. For example, <?php return [ ’user1’ => [ ’username’ => ’lmayert’, ’email’ => ’[email protected]’, ’auth_key’ => ’K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV’, ’password’ => ’$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/ iK0r3jRuwQEs2ldRu.a2’, ], ’user2’ => [ ’username’ => ’napoleon69’, ’email’ => ’[email protected]’, ’auth_key’ => ’dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q’, ’password’ => ’$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6 viYG5xJExU6’, ], ]; You may give an alias to a row so that later in your test, you may refer to the row via the alias. In the above example, the two rows are aliased as user1 and user2, respectively. Also, you do not need to specify the data for auto-incremental columns. Yii will automatically fill the actual values into the rows when the fixture is being loaded. Tip: You may customize the location of the data file by setting the yiitestActiveFixture::dataFile property. You may also override yiitestActiveFixture::getData() to provide the data. As we described earlier, a fixture may depend on other fixtures. For example, UserProfileFixture depends on UserFixture because the user profile table con- tains a foreign key pointing to the user table. The dependency is specified via the yiitestFixture::depends property, like the following, namespace apptestsfixtures; use yiitestActiveFixture; class UserProfileFixture extends ActiveFixture { public $modelClass = ’appmodelsUserProfile’; public $depends = [’apptestsfixturesUserFixture’]; } In the above, we have shown how to define a fixture about a DB table. To define a fixture not related with DB (e.g. a fixture about certain files
  • 395. 13.6. FIXTURES 389 and directories), you may extend from the more general base class yii testFixture and override the yiitestFixture::load() and yiitest Fixture::unload() methods. 13.6.2 Using Fixtures If you are using CodeCeption8 to test your code, you should consider using the yii2-codeception extension which has the built-in support for loading and accessing fixtures. If you are using other testing frameworks, you may use yiitestFixtureTrait in your test cases to achieve the same goal. In the following we will describe how to write a UserProfile unit test class using yii2-codeception. In your unit test class extending yiicodeceptionDbTestCase or yii codeceptionTestCase, declare which fixtures you want to use in the yii testFixtureTrait::fixtures() method. For example, namespace apptestsunitmodels; use yiicodeceptionDbTestCase; use apptestsfixturesUserProfileFixture; class UserProfileTest extends DbTestCase { public function fixtures() { return [ ’profiles’ => UserProfileFixture::className(), ]; } // ...test methods... } The fixtures listed in the fixtures() method will be automatically loaded before running every test method in the test case and unloaded after fin- ishing every test method. And as we described before, when a fixture is being loaded, all its dependent fixtures will be automatically loaded first. In the above example, because UserProfileFixture depends on UserFixture, when running any test method in the test class, two fixtures will be loaded sequentially: UserFixture and UserProfileFixture. When specifying fixtures in fixtures(), you may use either a class name or a configuration array to refer to a fixture. The configuration array will let you customize the fixture properties when the fixture is loaded. You may also assign an alias to a fixture. In the above example, the UserProfileFixture is aliased as profiles. In the test methods, you may then access a fixture object using its alias. For example, $this->profiles will return the UserProfileFixture object. 8 http://codeception.com/
  • 396. 390 CHAPTER 13. TESTING Because UserProfileFixture extends from ActiveFixture, you may further use the following syntax to access the data provided by the fixture: // returns the data row aliased as ’user1’ $row = $this->profiles[’user1’]; // returns the UserProfile model corresponding to the data row aliased as ’ user1’ $profile = $this->profiles(’user1’); // traverse every data row in the fixture foreach ($this->profiles as $row) ... Info: $this->profiles is still of UserProfileFixture type. The above access features are implemented through PHP magic methods. 13.6.3 Defining and Using Global Fixtures The fixtures described above are mainly used by individual test cases. In most cases, you also need some global fixtures that are applied to ALL or many test cases. An example is yiitestInitDbFixture which does two things: • Perform some common initialization tasks by executing a script located at @app/tests/fixtures/initdb.php; • Disable the database integrity check before loading other DB fixtures, and re-enable it after other DB fixtures are unloaded. Using global fixtures is similar to using non-global ones. The only differ- ence is that you declare these fixtures in yiicodeceptionTestCase:: globalFixtures() instead of fixtures(). When a test case loads fixtures, it will first load global fixtures and then non-global ones. By default, yiicodeceptionDbTestCase already declares InitDbFixture in its globalFixtures() method. This means you only need to work with @app /tests/fixtures/initdb.php if you want to do some initialization work before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures. 13.6.4 Organizing Fixture Classes and Data Files By default, fixture classes look for the corresponding data files under the data folder which is a sub-folder of the folder containing the fixture class files. You can follow this convention when working with simple projects. For big projects, chances are that you often need to switch different data files for the same fixture class for different tests. We thus recommend that you organize the data files in a hierarchical way that is similar to your class namespaces. For example,
  • 397. 13.6. FIXTURES 391 # under folder testsunitfixtures data components fixture_data_file1.php fixture_data_file2.php ... fixture_data_fileN.php models fixture_data_file1.php fixture_data_file2.php ... fixture_data_fileN.php # and so on In this way you will avoid collision of fixture data files between tests and use them as you need. Note: In the example above fixture files are named only for ex- ample purpose. In real life you should name them according to which fixture class your fixture classes are extending from. For example, if you are extending from yiitestActiveFixture for DB fixtures, you should use DB table names as the fixture data file names; If you are extending for yiimongodbActiveFixture for MongoDB fixtures, you should use collection names as the file names. The similar hierarchy can be used to organize fixture class files. Instead of using data as the root directory, you may want to use fixtures as the root directory to avoid conflict with the data files. 13.6.5 Summary In the above, we have described how to define and use fixtures. Below we summarize the typical workflow of running unit tests related with DB: 1. Use yii migrate tool to upgrade your test database to the latest version; 2. Run a test case: • Load fixtures: clean up the relevant DB tables and populate them with fixture data; • Perform the actual test; • Unload fixtures. 3. Repeat Step 2 until all tests finish. To be cleaned up below
  • 398. 392 CHAPTER 13. TESTING 13.7 Managing Fixtures // todo: this tutorial may be merged into test-fixture.md Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing different cases. With this data using your tests becoming more efficient and useful. Yii supports fixtures via the yii fixture command line tool. This tool supports: • Loading fixtures to different storage such as: RDBMS, NoSQL, etc; • Unloading fixtures in different ways (usually it is clearing storage); • Auto-generating fixtures and populating it with random data. 13.7.1 Fixtures format Fixtures are objects with different methods and configurations, refer to offi- cial documentation9 on them. Lets assume we have fixtures data to load: #users.php file under fixtures data path, by default @testsunitfixtures data return [ [ ’name’ => ’Chase’, ’login’ => ’lmayert’, ’email’ => ’[email protected]’, ’auth_key’ => ’K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV’, ’password’ => ’$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/ iK0r3jRuwQEs2ldRu.a2’, ], [ ’name’ => ’Celestine’, ’login’ => ’napoleon69’, ’email’ => ’[email protected]’, ’auth_key’ => ’dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q’, ’password’ => ’$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6 viYG5xJExU6’, ], ]; If we are using fixture that loads data into database then these rows will be applied to users table. If we are using nosql fixtures, for example mongodb fixture, then this data will be applied to users mongodb collection. In order to learn about implementing various loading strategies and more, refer to official documentation10. Above fixture example was auto-generated by yii2 -faker extension, read more about it in these section. Fixture classes name should not be plural. 9 https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md 10 https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md
  • 399. 13.7. MANAGING FIXTURES 393 13.7.2 Loading fixtures Fixture classes should be suffixed by Fixture class. By default fixtures will be searched under testsunitfixtures namespace, you can change this behavior with config or command options. Note that you can also append fixtures data to already existing ones with command option --append. You can exclude some fixtures due load or unload by specifying - before its name like -User. To load fixture, run the following command: yii fixture/load <fixture_name> The required fixture_name parameter specifies a fixture name which data will be loaded. You can load several fixtures at once. Below are correct formats of this command: // load ‘User‘ fixture yii fixture/load User // same as above, because default action of "fixture" command is "load" yii fixture User // load several fixtures yii fixture User UserProfile //load fixture, but don’t clean storage before load and just append to already existed data yii fixture User --append // load all fixtures yii fixture/load "*" // same as above yii fixture "*" // load all fixtures except ones yii fixture "*" -DoNotLoadThisOne // load fixtures, but search them in different namespace. By default namespace is: testsunitfixtures. yii fixture User --namespace=’aliasmycustomnamespace’ // load global fixture ‘somenamespaceCustomFixture‘ before other fixtures will be loaded. // By default this option is set to ‘InitDbFixture‘ to disable/enable integrity checks. You can specify several // global fixtures separated by comma. yii fixture User --globalFixtures=’somenamespaceCustom’ 13.7.3 Unloading fixtures To unload fixture, run the following command:
  • 400. 394 CHAPTER 13. TESTING // unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture ). yii fixture/unload User // Unload several fixtures yii fixture/unload User,UserProfile // unload all fixtures yii fixture/unload "*" // unload all fixtures except ones yii fixture/unload "*" -DoNotUnloadThisOne Same command options like: namespace, globalFixtures also can be applied to this command. 13.7.4 Configure Command Globally While command line options allow us to configure the migration command on-the-fly, sometimes we may want to configure the command once for all. For example you can configure different migration path as follows: ’controllerMap’ => [ ’fixture’ => [ ’class’ => ’yiiconsolecontrollersFixtureController’, ’namespace’ => ’myaliassomecustomnamespace’, ’globalFixtures’ => [ ’somenamespaceFoo’, ’othernamespaceBar’ ], ], ] 13.7.5 Auto-generating fixtures Yii also can auto-generate fixtures for you based on some template. You can generate your fixtures with different data on different languages and formats. These feature is done by Faker11 library and yii2-faker extension. See extension guide12 for more docs. 11 https://github.com/fzaninotto/Faker 12 https://github.com/yiisoft/yii2/tree/master/extensions/faker
  • 401. Chapter 14 Special Topics 14.1 Advanced application template Note: This section is under development. This template is for large projects developed in teams where the backend is divided from the frontend, application is deployed to multiple servers etc. This application template also goes a bit further regarding features and provides essential database, signup and password restore out of the box. 14.1.1 Installation Install via Composer If you do not have Composer1, you may download it from http://getcomposer.org/2 or run the following command on Linux/Unix/MacOS: curl -sS http://getcomposer.org/installer | php You can then install the application using the following command: php composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta2" php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2- app-advanced /path/to/yii-application 14.1.2 Getting started After you install the application, you have to conduct the following steps to initialize the installed application. You only need to do these once for all. 1. Execute the init command and select dev as environment. php /path/to/yii-application/init 1 http://getcomposer.org/ 2 http://getcomposer.org/ 395
  • 402. 396 CHAPTER 14. SPECIAL TOPICS Otherwise, in production execute init in non-interactive mode. php /path/to/yii-application/init --env=Production overwrite=All 2. Create a new database and adjust the components.db configuration in common/config/main-local.php accordingly. 3. Apply migrations with console command yii migrate. 4. Set document roots of your web server: • for frontend /path/to/yii-application/frontend/web/ and using the URL http://frontend/ • for backend /path/to/yii-application/backend/web/ and using the URL http://backend/ 14.1.3 Directory structure The root directory contains the following subdirectories: • backend - backend web application. • common - files common to all applications. • console - console application. • environments - environment configs. • frontend - frontend web application. Root directory contains a set of files. • .gitignore contains a list of directories ignored by git version system. If you need something never get to your source code repository, add it there. • composer.json - Composer config described in detail below. • init - initialization script described in “Composer config described in detail below”. • init.bat - same for Windows. • LICENSE.md - license info. Put your project license there. Especially when opensourcing. • README.md - basic info about installing template. Consider replacing it with information about your project and its installation.
  • 403. 14.1. ADVANCED APPLICATION TEMPLATE 397 • requirements.php - Yii requirements checker. • yii - console application bootstrap. • yii.bat - same for Windows. 14.1.4 Predefined path aliases • @yii - framework directory. • @app - base path of currently running application. • @common - common directory. • @frontend - frontend web application directory. • @backend - backend web application directory. • @console - console directory. • @runtime - runtime directory of currently running web application. • @vendor - Composer vendor directory. • @web - base URL of currently running web application. • @webroot - web root directory of currently running web application. The aliases specific to the directory structure of the advanced application (@common, @frontend, @backend, and @console) are defined in common/config/bootstrap .php. 14.1.5 Applications There are three applications in advanced template: frontend, backend and console. Frontend is typically what is presented to end user, the project itself. Backend is admin panel, analytics and such functionality. Console is typically used for cron jobs and low-level server management. Also it’s used during application deployment and handles migrations and assets. There’s also a common directory that contains files used by more than one application. For example, User model. frontend and backend are both web applications and both contain the web directory. That’s the webroot you should point your web server to. Each application has its own namespace and alias corresponding to its name. Same applies to common directory.
  • 404. 398 CHAPTER 14. SPECIAL TOPICS 14.1.6 Configuration and environments There are multiple problems with a typical approach to configuration: • Each team member has its own configuration options. Committing such config will affect other team members. • Production database password and API keys should not end up in the repository. • There are multiple server environments: development, testing, produc- tion. Each should have its own configuration. • Defining all configuration options for each case is very repetitive and takes too much time to maintain. In order to solve these issues Yii introduces a simple environments concept. Each environment is represented by a set of files under the environments directory. The init command is used to switch between these. What it really does is copy everything from the environment directory over to the root directory where all applications are. Typically environment contains application bootstrap files such as index .php and config files suffixed with -local.php. These are added to .gitignore and never added to source code repository. In order to avoid duplication configurations are overriding each other. For example, the frontend reads configuration in the following order: • common/config/main.php • common/config/main-local.php • frontend/config/main.php • frontend/config/main-local.php Parameters are read in the following order: • common/config/params.php • common/config/params-local.php • frontend/config/params.php • frontend/config/params-local.php The later config file overrides the former. Here’s the full scheme:
  • 405. 14.1. ADVANCED APPLICATION TEMPLATE 399 14.1.7 Configuring Composer After the application template is installed it’s a good idea to adjust default composer.json that can be found in the root directory: { "name": "yiisoft/yii2-app-advanced", "description": "Yii 2 Advanced Application Template", "keywords": ["yii", "framework", "advanced", "application template"], "homepage": "http://www.yiiframework.com/", "type": "project", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/yiisoft/yii2/issues?state=open", "forum": "http://www.yiiframework.com/forum/",
  • 406. 400 CHAPTER 14. SPECIAL TOPICS "wiki": "http://www.yiiframework.com/wiki/", "irc": "irc://irc.freenode.net/yii", "source": "https://github.com/yiisoft/yii2" }, "minimum-stability": "dev", "require": { "php": ">=5.4.0", "yiisoft/yii2": "*", "yiisoft/yii2-swiftmailer": "*", "yiisoft/yii2-bootstrap": "*", "yiisoft/yii2-debug": "*", "yiisoft/yii2-gii": "*" }, "scripts": { "post-create-project-cmd": [ "yiicomposerInstaller::setPermission" ] }, "extra": { "writable": [ "backend/runtime", "backend/web/assets", "console/runtime", "console/migrations", "frontend/runtime", "frontend/web/assets" ] } } First we’re updating basic information. Change name, description, keywords, homepage and support to match your project. Now the interesting part. You can add more packages your applica- tion needs to the require section. All these packages are coming from pack- agist.org3 so feel free to browse the website for useful code. After your composer.json is changed you can run php composer.phar update --prefer-dist, wait till packages are downloaded and installed and then just use them. Autoloading of classes will be handled automatically. 14.1.8 Creating links from backend to frontend Often it’s required to create links from the backend application to the fron- tend application. Since the frontend application may contain its own URL manager rules you need to duplicate that for the backend application by naming it differently: return [ ’components’ => [ 3 https://packagist.org/
  • 407. 14.2. CREATING YOUR OWN APPLICATION STRUCTURE 401 ’urlManager’ => [ // here is your normal backend url manager config ], ’urlManagerFrontend’ => [ // here is your frontend URL manager config ], ], ]; After it is done, you can get an URL pointing to frontend like the following: echo Yii::$app->urlManagerFrontend->createUrl(...); 14.2 Creating your own Application structure Note: This section is under development. While the basic and advanced application templates are great for most of your needs, you may want to create your own application template with which to start your projects. Application templates in Yii are simply repositories containing a composer .json file, and registered as a Composer package. Any repository can be identified as a Composer package, making it installable via create-project Composer command. Since it’s a bit too much to start building your entire template from scratch, it is better to use one of the built-in templates as a base. Let’s use the basic template here. 14.2.1 Clone the Basic Template The first step is to clone the basic Yii template’s Git repository: git clone [email protected]:yiisoft/yii2-app-basic.git Then wait for the repository to be downloaded to your computer. Since the changes made to the template won’t be pushed back, you can delete the .git diretory and all of its contents from the download. 14.2.2 Modify the Files Next, you’ll want to modify the composer.json to reflect your template. Change the name, description, keywords, homepage, license, and support values to de- scribe your new template. Also adjust the require, require-dev, suggest, and other options to match your template’s requirements. Note: In the composer.json file, use the writable parameter under extra to specify per file permissions to be set after an application is created using the template.
  • 408. 402 CHAPTER 14. SPECIAL TOPICS Next, actually modify the structure and contents of the application as you would like the default to be. Finally, update the README file to be applic- able to your template. 14.2.3 Make a Package With the template defined, create a Git repository from it, and push your files there. If you’re going to open source your template, Github4 is the best place to host it. If you intend to keep your template non-collaborative, any Git repository site will do. Next, you need to register your package for Composer’s sake. For public templates, the package should be registered at Packagist5. For private tem- plates, it is a bit more tricky to register the packge. For instructions, see the Composer documentation6. 14.2.4 Use the Template That’s all that’s required to create a new Yii application template. Now you can create projects using your template: php composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta2" php composer.phar create-project --prefer-dist --stability=dev mysoft/yii2- app-coolone new-project 14.3 Console applications Note: This section is under development. Yii has full featured support for console applications, whose structure is very similar to a Yii web application. A console application consists of one or more yiiconsoleController classes, which are often referred to as “commands” in the console environment. Each controller can also have one or more actions, just like web controllers. 14.3.1 Usage You execute a console controller action using the following syntax: yii <route> [--option1=value1 --option2=value2 ... argument1 argument2 ...] For example, the yiiconsolecontrollersMigrateController::actionCreate() with yiiconsolecontrollersMigrateController::$migrationTable set can be called from command line like so: 4 http://githumb.com 5 https://packagist.org/ 6 https://getcomposer.org/doc/05-repositories.md#hosting-your-own
  • 409. 14.3. CONSOLE APPLICATIONS 403 yii migrate/create --migrationTable=my_migration In the above yii is the console application entry script described below. 14.3.2 Entry script The console application entry script is equivalent to the index.php bootstrap file used for the web application. The console entry script is typically called yii, and located in your application’s root directory. The contents of the console application entry script contains code like the following: #!/usr/bin/env php <?php /** * Yii console bootstrap file. */ defined(’YII_DEBUG’) or define(’YII_DEBUG’, true); // fcgi doesn’t have STDIN and STDOUT defined by default defined(’STDIN’) or define(’STDIN’, fopen(’php://stdin’, ’r’)); defined(’STDOUT’) or define(’STDOUT’, fopen(’php://stdout’, ’w’)); require(__DIR__ . ’/vendor/autoload.php’); require(__DIR__ . ’/vendor/yiisoft/yii2/Yii.php’); $config = require(__DIR__ . ’/config/console.php’); $application = new yiiconsoleApplication($config); $exitCode = $application->run(); exit($exitCode); This script will be created as part of your application; you’re free to edit it to suit your needs. The YII_DEBUG constant can be set false if you do not want to see a stack trace on error, and/or if you want to improve the overall performance. In both basic and advanced application templates, the console application entry script has debugging enabled to provide a more developer-friendly environment. 14.3.3 Configuration As can be seen in the code above, the console application uses its own con- figuration file, named console.php. In this file you should configure various application components and properties for the console application in partic- ular. If your web application and the console application share a lot of config- uration parameters and values, you may consider moving the common parts into a separate file, and including this file in both of the application configur- ations (web and console). You can see an example of this in the “advanced” application template.
  • 410. 404 CHAPTER 14. SPECIAL TOPICS Sometimes, you may want to run a console command using an application configuration that is different from the one specified in the entry script. For example, you may want to use the yii migrate command to upgrade your test databases, which are configured in each individual test suite. To do change the configuration dynamically, simply specify a custom application configuration file via the appconfig option when executing the command: yii <route> --appconfig=path/to/config.php ... Note: When using * in console don’t forget to quote it as "*" in order to avoid executing it as a shell command. 14.3.4 Creating your own console commands Console Controller and Action A console command is defined as a controller class extending from yii consoleController. In the controller class, you define one or more actions that correspond to sub-commands of the controller. Within each action, you write code that implements the appropriate tasks for that particular sub- command. When running a command, you need to specify the route to the controller action. For example, the route migrate/create invokes the sub-command that corresponds to the yiiconsolecontrollersMigrateController:: actionCreate() action method. If a route offered during execution does not contain an action ID, the default action will be executed (as with a web controller). Options By overriding the yiiconsoleController::options() method, you can specify options that are available to a console command (controller/actionID). The method should return a list of the controller class’s public properties. When running a command, you may specify the value of an option using the syntax --OptionName=OptionValue. This will assign OptionValue to the OptionName property of the controller class. If the default value of an option is of an array type and you set this option while running the command, the option value will be converted into an array by splitting the input string on any commas. Arguments Besides options, a command can also receive arguments. The arguments will be passed as the parameters to the action method corresponding to the requested sub-command. The first argument corresponds to the first parameter, the second corresponds to the second, and so on. If not enough
  • 411. 14.3. CONSOLE APPLICATIONS 405 arguments are provided when the command is called, the corresponding para- meters will take the declared default values, if defined. If no default value is set, and no value is provided at runtime, the command will exit with an error. You may use the array type hint to indicate that an argument should be treated as an array. The array will be generated by splitting the input string on commas. The follow examples show how to declare arguments: class ExampleController extends yiiconsoleController { // The command "yii example/create test" will call "actionCreate(’test’) " public function actionCreate($name) { ... } // The command "yii example/index city" will call "actionIndex(’city’, ’ name’)" // The command "yii example/index city id" will call "actionIndex(’city ’, ’id’)" public function actionIndex($category, $order = ’name’) { ... } // The command "yii example/add test" will call "actionAdd([’test’])" // The command "yii example/add test1,test2" will call "actionAdd([’ test1’, ’test2’])" public function actionAdd(array $name) { ... } } Exit Code Using exit codes is a best practice for console application development. Con- ventionally, a command returns 0 to indicate that everything is OK. If the command returns a number greater than zero, that’s considered to be indic- ative of an error. The number returned will be the error code, potentially usable to find out details about the error. For example 1 could stand gener- ally for an unknown error and all codes above would be reserved for specific cases: input errors, missing files, and so forth. To have your console command return an exit code, simply return an integer in the controller action method: public function actionIndex() { if (/* some problem */) { echo "A problem occured!n"; return 1; } // do something return 0; } There are some predefined constants you can use:
  • 412. 406 CHAPTER 14. SPECIAL TOPICS • Controller::EXIT_CODE_NORMAL with value of 0; • Controller::EXIT_CODE_ERROR with value of 1. 14.4 Core Validators Yii provides a set of commonly used core validators, found primarily un- der the yiivalidators namespace. Instead of using lengthy validator class names, you may use aliases to specify the use of these core validators. For example, you can use the alias required to refer to the yiivalidators RequiredValidator class: public function rules() { return [ [[’email’, ’password’], ’required’], ]; } The yiivalidatorsValidator::builtInValidators property declares all supported validator aliases. In the following, we will describe the main usage and properties of every core validator. 14.4.1 yiivalidatorsBooleanValidator [ // checks if "selected" is either 0 or 1, regardless of data type [’selected’, ’boolean’], // checks if "deleted" is of boolean type, either true or false [’deleted’, ’boolean’, ’trueValue’ => true, ’falseValue’ => false, ’ strict’ => true], ] This validator checks if the input value is a boolean. • trueValue: the value representing true. Defaults to ’1’. • falseValue: the value representing false. Defaults to ’0’. • strict: whether the type of the input value should match that of trueValue and falseValue. Defaults to false. Note: Because data input submitted via HTML forms are all strings, you normally should leave the yiivalidatorsBooleanValidator ::strict property as false.
  • 413. 14.4. CORE VALIDATORS 407 14.4.2 yiicaptchaCaptchaValidator [ [’verificationCode’, ’captcha’], ] This validator is usually used together with yiicaptchaCaptchaAction and yiicaptchaCaptcha to make sure an input is the same as the verific- ation code displayed by yiicaptchaCaptcha widget. • caseSensitive: whether the comparison of the verification code is case sensitive. Defaults to false. • captchaAction: the route corresponding to the yiicaptchaCaptchaAction that renders the CAPTCHA image. Defaults to ’site/captcha’. • skipOnEmpty: whether the validation can be skipped if the input is empty. Defaults to false, which means the input is required. 14.4.3 yiivalidatorsCompareValidator [ // validates if the value of "password" attribute equals to that of " password_repeat" [’password’, ’compare’], // validates if age is greater than or equal to 30 [’age’, ’compare’, ’compareValue’ => 30, ’operator’ => ’>=’], ] This validator compares the specified input value with another one and make sure if their relationship is as specified by the operator property. • compareAttribute: the name of the attribute whose value should be com- pared with. When the validator is being used to validate an attribute, the default value of this property would be the name of the attribute suffixed with _repeat. For example, if the attribute being validated is password, then this property will default to password_repeat. • compareValue: a constant value that the input value should be compared with. When both of this property and compareAttribute are specified, this property will take precedence. • operator: the comparison operator. Defaults to ==, meaning checking if the input value is equal to that of compareAttribute or compareValue. The following operators are supported: – ==: check if two values are equal. The comparison is done is non- strict mode. – ===: check if two values are equal. The comparison is done is strict mode.
  • 414. 408 CHAPTER 14. SPECIAL TOPICS – !=: check if two values are NOT equal. The comparison is done is non-strict mode. – !==: check if two values are NOT equal. The comparison is done is strict mode. – >: check if value being validated is greater than the value being compared with. – >=: check if value being validated is greater than or equal to the value being compared with. – <: check if value being validated is less than the value being com- pared with. – <=: check if value being validated is less than or equal to the value being compared with. 14.4.4 yiivalidatorsDateValidator [ [[’from’, ’to’], ’date’], ] This validator checks if the input value is a date, time or datetime in a proper format. Optionally, it can convert the input value into a UNIX timestamp and store it in an attribute specified via yiivalidatorsDateValidator:: timestampAttribute. • format: the date/time format that the value being validated should be in. This can be a date time pattern as described in the ICU manual7. Alternatively this can be a string prefixed with php: representing a format that can be recognized by the PHP Datetime class. Please refer to http://php.net/manual/en/datetime.createfromformat.php on supported formats. If this is not set, it will take the value of Yii::$app ->formatter->dateFormat. • timestampAttribute: the name of the attribute to which this validator may assign the UNIX timestamp converted from the input date/time. 14.4.5 yiivalidatorsDefaultValueValidator [ // set "age" to be null if it is empty [’age’, ’default’, ’value’ => null], // set "country" to be "USA" if it is empty [’country’, ’default’, ’value’ => ’USA’], 7 http://userguide.icu-project.org/formatparse/datetime# TOC-Date-Time-Format-Syntax
  • 415. 14.4. CORE VALIDATORS 409 // assign "from" and "to" with a date 3 days and 6 days from today, if they are empty [[’from’, ’to’], ’default’, ’value’ => function ($model, $attribute) { return date(’Y-m-d’, strtotime($attribute === ’to’ ? ’+3 days’ : ’+6 days’)); }], ] This validator does not validate data. Instead, it assigns a default value to the attributes being validated if the attributes are empty. • value: the default value or a PHP callable that returns the default value which will be assigned to the attributes being validated if they are empty. The signature of the PHP callable should be as follows, function foo($model, $attribute) { // ... compute $value ... return $value; } Info: How to determine if a value is empty or not is a separate topic covered in the Empty Values section. 14.4.6 yiivalidatorsNumberValidator [ // checks if "salary" is a double number [’salary’, ’double’], ] This validator checks if the input value is a double number. It is equivalent to the number validator. • max: the upper limit (inclusive) of the value. If not set, it means the validator does not check the upper limit. • min: the lower limit (inclusive) of the value. If not set, it means the validator does not check the lower limit. 14.4.7 yiivalidatorsEmailValidator [ // checks if "email" is a valid email address [’email’, ’email’], ] This validator checks if the input value is a valid email address. • allowName: whether to allow name in the email address (e.g. John Smith <[email protected]>). Defaults to false.
  • 416. 410 CHAPTER 14. SPECIAL TOPICS • checkDNS, whether to check whether the email’s domain exists and has either an A or MX record. Be aware that this check may fail due to temporary DNS problems, even if the email address is actually valid. Defaults to false. • enableIDN, whether the validation process should take into account IDN (internationalized domain names). Defaults to false. Note that in order to use IDN validation you have to install and enable the intl PHP extension, or an exception would be thrown. 14.4.8 yiivalidatorsExistValidator [ // a1 needs to exist in the column represented by the "a1" attribute [’a1’, ’exist’], // a1 needs to exist, but its value will use a2 to check for the existence [’a1’, ’exist’, ’targetAttribute’ => ’a2’], // a1 and a2 need to exist together, and they both will receive error message [[’a1’, ’a2’], ’exist’, ’targetAttribute’ => [’a1’, ’a2’]], // a1 and a2 need to exist together, only a1 will receive error message [’a1’, ’exist’, ’targetAttribute’ => [’a1’, ’a2’]], // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value) [’a1’, ’exist’, ’targetAttribute’ => [’a2’, ’a1’ => ’a3’]], // a1 needs to exist. If a1 is an array, then every element of it must exist. [’a1’, ’exist’, ’allowArray’ => true], ] This validator checks if the input value can be found in a table column. It only works with Active Record model attributes. It supports validation against either a single column or multiple columns. • targetClass: the name of the Active Record class that should be used to look for the input value being validated. If not set, the class of the model currently being validated will be used. • targetAttribute: the name of the attribute in targetClass that should be used to validate the existence of the input value. If not set, it will use the name of the attribute currently being validated. You may use an array to validate the existence of multiple columns at the same time. The array values are the attributes that will be used to validate the existence, while the array keys are the attributes whose values are
  • 417. 14.4. CORE VALIDATORS 411 to be validated. If the key and the value are the same, you can just specify the value. • filter: additional filter to be applied to the DB query used to check the existence of the input value. This can be a string or an array representing the additional query condition (refer to yiidbQuery:: where() on the format of query condition), or an anonymous function with the signature function ($query), where $query is the yiidbQuery object that you can modify in the function. • allowArray: whether to allow the input value to be an array. Defaults to false. If this property is true and the input is an array, then every ele- ment of the array must exist in the target column. Note that this prop- erty cannot be set true if you are validating against multiple columns by setting targetAttribute as an array. 14.4.9 yiivalidatorsFileValidator [ // checks if "primaryImage" is an uploaded image file in PNG, JPG or GIF format. // the file size must be less than 1MB [’primaryImage’, ’file’, ’extensions’ => [’png’, ’jpg’, ’gif’], ’maxSize ’ => 1024*1024*1024], ] This validator checks if the input is a valid uploaded file. • extensions: a list of file name extensions that are allowed to be up- loaded. This can be either an array or a string consisting of file exten- sion names separated by space or comma (e.g. “gif, jpg”). Extension names are case-insensitive. Defaults to null, meaning all file name extensions are allowed. • mimeTypes: a list of file MIME types that are allowed to be uploaded. This can be either an array or a string consisting of file MIME types separated by space or comma (e.g. “image/jpeg, image/png”). Mime type names are case-insensitive. Defaults to null, meaning all MIME types are allowed. • minSize: the minimum number of bytes required for the uploaded file. Defaults to null, meaning no lower limit. • maxSize: the maximum number of bytes allowed for the uploaded file. Defaults to null, meaning no upper limit. • maxFiles: the maximum number of files that the given attribute can hold. Defaults to 1, meaning the input must be a single uploaded file.
  • 418. 412 CHAPTER 14. SPECIAL TOPICS If it is greater than 1, then the input must be an array consisting of at most maxFiles number of uploaded files. • checkExtensionByMimeType: whether to check the file extension by the file’s MIME type. If the extension produced by MIME type check differs from the uploaded file extension, the file will be considered as invalid. Defaults to true, meaning perform such check. FileValidator is used together with yiiwebUploadedFile. Please refer to the Uploading Files section for complete coverage about uploading files and performing validation about the uploaded files. 14.4.10 yiivalidatorsFilterValidator [ // trim "username" and "email" inputs [[’username’, ’email’], ’filter’, ’filter’ => ’trim’, ’skipOnArray’ => true], // normalize "phone" input [’phone’, ’filter’, ’filter’ => function ($value) { // normalize phone input here return $value; }], ] This validator does not validate data. Instead, it applies a filter on the input value and assigns it back to the attribute being validated. • filter: a PHP callback that defines a filter. This can be a global function name, an anonymous function, etc. The function signature must be function ($value) { return $newValue; }. This property must be set. • skipOnArray: whether to skip the filter if the input value is an array. Defaults to false. Note that if the filter cannot handle array input, you should set this property to be true. Otherwise some PHP error might occur. Tip: If you want to trim input values, you may directly use trim validator. 14.4.11 yiivalidatorsImageValidator [ // checks if "primaryImage" is a valid image with proper size [’primaryImage’, ’image’, ’extensions’ => ’png, jpg’, ’minWidth’ => 100, ’maxWidth’ => 1000, ’minHeight’ => 100, ’maxHeight’ => 1000, ], ]
  • 419. 14.4. CORE VALIDATORS 413 This validator checks if the input value represents a valid image file. It extends from the file validator and thus inherits all its properties. Besides, it supports the following additional properties specific for image validation purpose: • minWidth: the minimum width of the image. Defaults to null, meaning no lower limit. • maxWidth: the maximum width of the image. Defaults to null, meaning no upper limit. • minHeight: the minimum height of the image. Defaults to null, meaning no lower limit. • maxHeight: the maximum height of the image. Defaults to null, meaning no upper limit. 14.4.12 yiivalidatorsRangeValidator [ // checks if "level" is 1, 2 or 3 [’level’, ’in’, ’range’ => [1, 2, 3]], ] This validator checks if the input value can be found among the given list of values. • range: a list of given values within which the input value should be looked for. • strict: whether the comparison between the input value and the given values should be strict (both the type and value must be the same). Defaults to false. • not: whether the validation result should be inverted. Defaults to false. When this property is set true, the validator checks if the input value is NOT among the given list of values. • allowArray: whether to allow the input value to be an array. When this is true and the input value is an array, every element in the array must be found in the given list of values, or the validation would fail. 14.4.13 yiivalidatorsNumberValidator [ // checks if "age" is an integer [’age’, ’integer’], ]
  • 420. 414 CHAPTER 14. SPECIAL TOPICS This validator checks if the input value is an integer. • max: the upper limit (inclusive) of the value. If not set, it means the validator does not check the upper limit. • min: the lower limit (inclusive) of the value. If not set, it means the validator does not check the lower limit. 14.4.14 yiivalidatorsRegularExpressionValidator [ // checks if "username" starts with a letter and contains only word characters [’username’, ’match’, ’pattern’ => ’/^[a-z]w*$/i’] ] This validator checks if the input value matches the specified regular expres- sion. • pattern: the regular expression that the input value should match. This property must be set, or an exception will be thrown. • not: whether to invert the validation result. Defaults to false, meaning the validation succeeds only if the input value matches the pattern. If this is set true, the validation is considered successful only if the input value does NOT match the pattern. 14.4.15 yiivalidatorsNumberValidator [ // checks if "salary" is a number [’salary’, ’number’], ] This validator checks if the input value is a number. It is equivalent to the double validator. • max: the upper limit (inclusive) of the value. If not set, it means the validator does not check the upper limit. • min: the lower limit (inclusive) of the value. If not set, it means the validator does not check the lower limit. 14.4.16 yiivalidatorsRequiredValidator [ // checks if both "username" and "password" are not empty [[’username’, ’password’], ’required’], ]
  • 421. 14.4. CORE VALIDATORS 415 This validator checks if the input value is provided and not empty. • requiredValue: the desired value that the input should be. If not set, it means the input should not be empty. • strict: whether to check data types when validating a value. Defaults to false. When requiredValue is not set, if this property is true, the validator will check if the input value is not strictly null; If this property is false, the validator will use a loose rule to determine a value is empty or not. When requiredValue is set, the comparison between the input and requiredValue will also check data types if this property is true. Info: How to determine if a value is empty or not is a separate topic covered in the Empty Values section. 14.4.17 yiivalidatorsSafeValidator [ // marks "description" to be a safe attribute [’description’, ’safe’], ] This validator does not perform data validation. Instead, it is used to mark an attribute to be a safe attribute. 14.4.18 yiivalidatorsStringValidator [ // checks if "username" is a string whose length is between 4 and 24 [’username’, ’string’, ’length’ => [4, 24]], ] This validator checks if the input value is a valid string with certain length. • length: specifies the length limit of the input string being validated. This can be specified in one of the following forms: – an integer: the exact length that the string should be of; – an array of one element: the minimum length of the input string (e.g. [8]). This will overwrite min. – an array of two elements: the minimum and maximum lengths of the input string (e.g. [8, 128]). This will overwrite both min and max. • min: the minimum length of the input string. If not set, it means no minimum length limit. • max: the maximum length of the input string. If not set, it means no maximum length limit.
  • 422. 416 CHAPTER 14. SPECIAL TOPICS • encoding: the encoding of the input string to be validated. If not set, it will use the application’s yiibaseApplication::charset value which defaults to UTF-8. 14.4.19 yiivalidatorsFilterValidator [ // trims the white spaces surrounding "username" and "email" [[’username’, ’email’], ’trim’], ] This validator does not perform data validation. Instead, it will trim the surrounding white spaces around the input value. Note that if the input value is an array, it will be ignored by this validator. 14.4.20 yiivalidatorsUniqueValidator [ // a1 needs to be unique in the column represented by the "a1" attribute [’a1’, ’unique’], // a1 needs to be unique, but column a2 will be used to check the uniqueness of the a1 value [’a1’, ’unique’, ’targetAttribute’ => ’a2’], // a1 and a2 need to be unique together, and they both will receive error message [[’a1’, ’a2’], ’unique’, ’targetAttribute’ => [’a1’, ’a2’]], // a1 and a2 need to be unique together, only a1 will receive error message [’a1’, ’unique’, ’targetAttribute’ => [’a1’, ’a2’]], // a1 needs to be unique by checking the uniqueness of both a2 and a3 ( using a1 value) [’a1’, ’unique’, ’targetAttribute’ => [’a2’, ’a1’ => ’a3’]], ] This validator checks if the input value is unique in a table column. It only works with Active Record model attributes. It supports validation against either a single column or multiple columns. • targetClass: the name of the Active Record class that should be used to look for the input value being validated. If not set, the class of the model currently being validated will be used. • targetAttribute: the name of the attribute in targetClass that should be used to validate the uniqueness of the input value. If not set, it will use the name of the attribute currently being validated. You may use an array to validate the uniqueness of multiple columns at the same time. The array values are the attributes that will be used to validate the uniqueness, while the array keys are the attributes whose values
  • 423. 14.5. INTERNATIONALIZATION 417 are to be validated. If the key and the value are the same, you can just specify the value. • filter: additional filter to be applied to the DB query used to check the uniqueness of the input value. This can be a string or an array representing the additional query condition (refer to yiidbQuery:: where() on the format of query condition), or an anonymous function with the signature function ($query), where $query is the yiidbQuery object that you can modify in the function. 14.4.21 yiivalidatorsUrlValidator [ // checks if "website" is a valid URL. Prepend "http://" to the "website " attribute // if it does not have a URI scheme [’website’, ’url’, ’defaultScheme’ => ’http’], ] This validator checks if the input value is a valid URL. • validSchemes: an array specifying the URI schemes that should be con- sidered valid. Defaults to [’http’, ’https’], meaning both http and https URLs are considered to be valid. • defaultScheme: the default URI scheme to be prepended to the input if it does not have the scheme part. Defaults to null, meaning do not modify the input value. • enableIDN: whether the validator should take into account IDN (inter- nationalized domain names). Defaults to false. Note that in order to use IDN validation you have to install and enable the intl PHP extension, otherwise an exception would be thrown. 14.5 Internationalization Note: This section is under development. Internationalization (I18N) refers to the process of designing a software ap- plication so that it can be adapted to various languages and regions without engineering changes. For Web applications, this is of particular importance because the potential users may be worldwide. Yii offers several tools that help with internationalisation of a website such as [message translation][], [number and date formatting][].
  • 424. 418 CHAPTER 14. SPECIAL TOPICS 14.5.1 Locale and Language There are two languages defined in the Yii application: yiibaseApplication ::$sourceLanguage and yiibaseApplication::$language. Source language is the language original application messages are written in directly in the code such as: echo Yii::t(’app’, ’I am a message!’); The target language is the language that should be used to display the cur- rent page i.e. the language that original messages need to be translated to. It is defined in the application configuration like the following: return [ ’id’ => ’applicationID’, ’basePath’ => dirname(__DIR__), // ... ’language’ => ’ru-RU’, // <- here! // ... ] Tip: The default value for the yiibaseApplication::$sourceLanguage is English and it is recommended to keep this value. The reason is that it’s easier to find people translating from English to any language than from non-English to non-English. You may set the application language at runtime to set it to a language the user has chosen. This has to be done at a point before any output is generated so that it affects all the output correctly. Therefor just change the application property to the desired value: Yii::$app->language = ’zh-CN’; The format for the language/locale is ll-CC where ll is a two- or three-letter lowercase code for a language according to ISO-6398 and CC is the country code according to ISO-31669. Note: For more information on the concept and syntax of locales, check the documentation of the ICU project10. 14.5.2 Message translation Message translation is used to translate messages that are ouput by an ap- plicaiton to different languages so that users from different countries can use the application in their native language. 8 http://www.loc.gov/standards/iso639-2/ 9 http://www.iso.org/iso/en/prods-services/iso3166ma/ 02iso-3166-code-lists/list-en1.html 10 http://userguide.icu-project.org/locale#TOC-The-Locale-Concept
  • 425. 14.5. INTERNATIONALIZATION 419 The message translation feature in Yii works simply as finding a trans- lation of the message from a source language into a target language. To use the message translation feature you wrap your original message strings with a call to the Yii::t() method. The first parameter of this method takes a category which helps to distingish the source of messages in differnet parts of the application and the second parameter is the message itself. echo Yii::t(’app’, ’This is a string to translate!’); Yii tries to load an appropriate translation according to the current yii baseApplication::$language from one of the message sources defined in the i18n application component. A message source is a set of files or a database that provides translation messages. The following configuration example defines a messages source that takes the messages from PHP files: ’components’ => [ // ... ’i18n’ => [ ’translations’ => [ ’app*’ => [ ’class’ => ’yiii18nPhpMessageSource’, //’basePath’ => ’@app/messages’, //’sourceLanguage’ => ’en-US’, ’fileMap’ => [ ’app’ => ’app.php’, ’app/error’ => ’error.php’, ], ], ], ], ], In the above app* is a pattern that specifies which categories are handled by the message source. In this case we’re handling everything that begins with app. Message files are located in @app/messages, the messages directory in your application directory. The yiii18nPhpMessageSource::fileMap ar- ray defines which file is to be used for which category. Instead of configuring fileMap you can rely on convention which is to use the category name as the file name (e.g. category app/error will result in the file name app/error.php under the yiii18nPhpMessageSource::basePath. When translating the message for Yii::t(’app’, ’This is a string to translate!’) and an application language ru-RU, Yii will first look for a file @app/messages/ru-RU/app.php to retrieve the list of available translations. If there is file ru-RU it will try ru as well before failing. Beside storing messages in PHP files (using yiii18nPhpMessageSource) Yii provides two other classes: • yiii18nGettextMessageSource that uses GNU Gettext MO or PO files.
  • 426. 420 CHAPTER 14. SPECIAL TOPICS • yiii18nDbMessageSource that uses a database. Named placeholders You can add parameters to a translation message that will be substituted with the corresponding value after translation. The format for this is to use curly brackets around the parameter name as you can see in the following example: $username = ’Alexander’; echo Yii::t(’app’, ’Hello, {username}!’, [ ’username’ => $username, ]); Note that the parameter assignment is without the brackets. Positional placeholders $sum = 42; echo Yii::t(’app’, ’Balance: {0}’, $sum); Tip: Try keep message strings meaningful and avoid using too many positional parameters. Remember that translator has source string only so it should be obvious about what will replace each placeholder. Advanced placeholder formatting In order to use advanced features you need to install and enable the intl PHP extension11. After installing and enabling it you will be able to use extended syntax for placeholders. Either short form {placeholderName, argumentType } that means default setting or full form {placeholderName, argumentType, argumentStyle} that allows you to specify formatting style. A complete reference is available at the ICU website12 but we will show some examples in the following. $sum = 42; echo Yii::t(’app’, ’Balance: {0, number}’, $sum); You can specify one of the built-in styles (integer, currency, percent): $sum = 42; echo Yii::t(’app’, ’Balance: {0, number, currency}’, $sum); Or specify a custom pattern: $sum = 42; echo Yii::t(’app’, ’Balance: {0, number, ,000,000000}’, $sum); 11 http://www.php.net/manual/en/intro.intl.php 12 http://icu-project.org/apiref/icu4c/classMessageFormat.html
  • 427. 14.5. INTERNATIONALIZATION 421 Formatting reference13. echo Yii::t(’app’, ’Today is {0, date}’, time()); Built in formats are short, medium, long, and full: echo Yii::t(’app’, ’Today is {0, date, short}’, time()); You may also specify a custom pattern: echo Yii::t(’app’, ’Today is {0, date, yyyy-MM-dd}’, time()); Formatting reference14. echo Yii::t(’app’, ’It is {0, time}’, time()); Built in formats are short, medium, long, and full: echo Yii::t(’app’, ’It is {0, time, short}’, time()); You may also specify a custom pattern: echo Yii::t(’app’, ’It is {0, date, HH:mm}’, time()); Formatting reference15. echo Yii::t(’app’, ’{n,number} is spelled as {n, spellout}’, [’n’ => 42]); echo Yii::t(’app’, ’You are {n, ordinal} visitor here!’, [’n’ => 42]); Will produce “You are 42nd visitor here!”. echo Yii::t(’app’, ’You are here for {n, duration} already!’, [’n’ => 47]); Will produce “You are here for 47 sec. already!”. 13 http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html 14 http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html 15 http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html
  • 428. 422 CHAPTER 14. SPECIAL TOPICS Plurals Different languages have different ways to inflect plurals. Some rules are very complex so it’s very handy that this functionality is provided without the need to specify inflection rule. Instead it only requires your input of inflected words in certain situations. echo Yii::t(’app’, ’There {n, plural, =0{are no cats} =1{is one cat} other{ are # cats}}!’, [’n’ => 0]); Will give us “There are no cats!”. In the plural rule arguments above =0 means exactly zero, =1 stands for exactly one other is for any other number. # is replaced with the n argument value. It’s not that simple for languages other than English. Here’s an example for Russian: Здесь {n, plural, котов=0{ нет} есть=1{ один кот} one{# кот} few{# кота} many{# котов} other{# кота}}! In the above it worth mentioning that =1 matches exactly n = 1 while one matches 21 or 101. Note that if you are using a placeholder twice and one time it’s used as plural another one should be used as number else you’ll get “Inconsistent types declared for an argument: U_ARGUMENT_TYPE_MISMATCH” error: Total {count, number} {count, plural, one{item} other{items}}. To learn which inflection forms you should specify for your language you can referrer to the rules reference at unicode.org16. Selections You can select phrases based on keywords. The pattern in this case specifies how to map keywords to phrases and provides a default phrase. echo Yii::t(’app’, ’{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!’, [ ’name’ => ’Snoopy’, ’gender’ => ’dog’, ]); Will produce “Snoopy is dog and it loves Yii!”. In the expression female and male are possible values. other handles values that do not match. Strings inside brackets are sub-expressions so could be just a string or a string with more placeholders. Specifying default translation You can specify default translations that will be used as a fallback for cat- egories that don’t match any other translation. This translation should be marked with *. In order to do it add the following to the application config: 16 http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_ plural_rules.html
  • 429. 14.5. INTERNATIONALIZATION 423 //configure i18n component ’i18n’ => [ ’translations’ => [ ’*’ => [ ’class’ => ’yiii18nPhpMessageSource’ ], ], ], Now you can use categories without configuring each one, which is similar to Yii 1.1 behavior. Messages for the category will be loaded from a file under the default translation basePath that is @app/messages: echo Yii::t(’not_specified_category’, ’message from unspecified category’); Message will be loaded from @app/messages/<LanguageCode>/not_specified_category .php. Translating module messages If you want to translate messages for a module and avoid using a single translation file for all messages, you can do it like the following: <?php namespace appmodulesusers; use Yii; class Module extends yiibaseModule { public $controllerNamespace = ’appmodulesuserscontrollers’; public function init() { parent::init(); $this->registerTranslations(); } public function registerTranslations() { Yii::$app->i18n->translations[’modules/users/*’] = [ ’class’ => ’yiii18nPhpMessageSource’, ’sourceLanguage’ => ’en-US’, ’basePath’ => ’@app/modules/users/messages’, ’fileMap’ => [ ’modules/users/validation’ => ’validation.php’, ’modules/users/form’ => ’form.php’, ... ], ]; }
  • 430. 424 CHAPTER 14. SPECIAL TOPICS public static function t($category, $message, $params = [], $language = null) { return Yii::t(’modules/users/’ . $category, $message, $params, $language); } } In the example above we are using wildcard for matching and then filtering each category per needed file. Instead of using fileMap you can simply use convention of category mapping to the same named file and use Module::t (’validation’, ’your custom validation message’) or Module::t(’form’, ’some form label’) directly. Translating widgets messages The same rule as applied for Modules above can be applied for widgets too, for example: <?php namespace appwidgetsmenu; use yiibaseWidget; use Yii; class Menu extends Widget { public function init() { parent::init(); $this->registerTranslations(); } public function registerTranslations() { $i18n = Yii::$app->i18n; $i18n->translations[’widgets/menu/*’] = [ ’class’ => ’yiii18nPhpMessageSource’, ’sourceLanguage’ => ’en-US’, ’basePath’ => ’@app/widgets/menu/messages’, ’fileMap’ => [ ’widgets/menu/messages’ => ’messages.php’, ], ]; } public function run() { echo $this->render(’index’); }
  • 431. 14.5. INTERNATIONALIZATION 425 public static function t($category, $message, $params = [], $language = null) { return Yii::t(’widgets/menu/’ . $category, $message, $params, $language); } } Instead of using fileMap you can simply use convention of category mapping to the same named file and use Menu::t(’messages’, ’new messages {messages} ’, [’{messages}’ => 10]) directly. Note: For widgets you also can use i18n views, same rules as for controllers are applied to them too. Translating framework messages Yii comes with default translation messages for validation errors and some other strings. These messages are all in the category yii. Sometimes you want to correct default framework message translation for your application. In order to do so configure the i18n application component like the following: ’i18n’ => [ ’translations’ => [ ’yii’ => [ ’class’ => ’yiii18nPhpMessageSource’, ’sourceLanguage’ => ’en-US’, ’basePath’ => ’@app/messages’ ], ], ], Now you can place your adjusted translations to @app/messages/<language>/ yii.php. Handling missing translations If the translation is missing at the source, Yii displays the requested message content. Such behavior is very convenient in case your raw message is a valid verbose text. However, sometimes it is not enough. You may need to perform some custom processing of the situation, when requested translation is miss- ing at the source. This can be achieved using the yiii18nMessageSource ::EVENT_MISSING_TRANSLATION-event of yiii18nMessageSource. For example to mark all missing translations with something notable, so they can be easily found at the page we first we need to setup event handler. This can be done in the application configuration: ’components’ => [
  • 432. 426 CHAPTER 14. SPECIAL TOPICS // ... ’i18n’ => [ ’translations’ => [ ’app*’ => [ ’class’ => ’yiii18nPhpMessageSource’, ’fileMap’ => [ ’app’ => ’app.php’, ’app/error’ => ’error.php’, ], ’on missingTranslation’ => [’appcomponents TranslationEventHandler’, ’handleMissingTranslation’] ], ], ], ], Now we need to implement our own event handler: <?php namespace appcomponents; use yiii18nMissingTranslationEvent; class TranslationEventHandler { public static function(MissingTranslationEvent $event) { $event->translatedMessage = "@MISSING: {$event->category}.{$event-> message} FOR LANGUAGE {$event->language} @"; } } If yiii18nMissingTranslationEvent::translatedMessage is set by the event handler it will be displayed as the translation result. Attention: each message source handles its missing translations separately. If you are using several message sources and wish them treat missing translation in the same way, you should assign corresponding event handler to each of them. 14.5.3 Views Instead of translating messages as described in the last section, you can also use i18n in your views to provide support for different languages. For example, if you have a view views/site/index.php and you want to create a special version for russian language of it, you create a ru-RU folder under the view path of the current controller/widget and put the file for russian language as follows views/site/ru-RU/index.php. Yii will then load the file for the current language if it exists and fall back to the original view file if none was found.
  • 433. 14.5. INTERNATIONALIZATION 427 Note: If language is specified as en-US and there are no corres- ponding views, Yii will try views under en before using original ones. 14.5.4 Formatting Number and Date values See the data formatter section for details. 14.5.5 Setting up your PHP environment Yii uses the PHP intl extension17 to provide most of its internationalization features such as the number and date formatting of the yiii18nFormatter class and the message formatting using yiii18nMessageFormatter. Both classes provides a fallback implementation that provides basic functionality in case intl is not installed. This fallback implementation however only works well for sites in english language and even there can not provide the rich set of features that is avialable with the PHP intl extension, so its installation is highly recommended. The PHP intl extension18 is based on the ICU library19 which provides the knowledge and formatting rules for all the different locales. According to this fact the formatting of dates and numbers and also the supported syntax available for message formatting differs between different versions of the ICU library that is compiled with you PHP binary. To ensure your website works with the same output in all environments it is recommended to install the PHP intl extension in all environments and verify that the version of the ICU library compiled with PHP is the same. To find out which version of ICU is used by PHP you can run the following script, which will give you the PHP and ICU version used. <?php echo "PHP: " . PHP_VERSION . "n"; echo "ICU: " . INTL_ICU_VERSION . "n"; We recommend an ICU version greater or equal to version ICU 49 to be able to use all the features described in this document. One major feature that is missing in Versions below 49 is the # placeholder in plural rules. See http://site.icu-project.org/download for a list of available ICU versions. Note that the version numbering has changed after the 4.8 release so that the first digits are now merged: the sequence is ICU 4.8, ICU 49, ICU 50. 17 http://php.net/manual/en/book.intl.php 18 http://php.net/manual/en/book.intl.php 19 http://site.icu-project.org/
  • 434. 428 CHAPTER 14. SPECIAL TOPICS 14.6 Mailing Note: This section is under development. Yii supports composition and sending of the email messages. However, the framework core provides only the content composition functionality and basic interface. Actual mail sending mechanism should be provided by the exten- sion, because different projects may require its different implementation and it usually depends on the external services and libraries. For the most common cases you can use yii2-swiftmailer20 official exten- sion. 14.6.1 Configuration Mail component configuration depends on the extension you have chosen. In general your application configuration should look like: return [ //.... ’components’ => [ ’mailer’ => [ ’class’ => ’yiiswiftmailerMailer’, ], ], ]; 14.6.2 Basic usage Once ‘mailer’ component is configured, you can use the following code to send an email message: Yii::$app->mailer->compose() ->setFrom(’[email protected]’) ->setTo(’[email protected]’) ->setSubject(’Message subject’) ->setTextBody(’Plain text content’) ->setHtmlBody(’<b>HTML content</b>’) ->send(); In above example method compose() creates an instance of the mail message, which then is populated and sent. You may put more complex logic in this process if needed: $message = Yii::$app->mailer->compose(); if (Yii::$app->user->isGuest) { $message->setFrom(’[email protected]’) } else { $message->setFrom(Yii::$app->user->identity->email) } 20 https://github.com/yiisoft/yii2/tree/master/extensions/swiftmailer
  • 435. 14.6. MAILING 429 $message->setTo(Yii::$app->params[’adminEmail’]) ->setSubject(’Message subject’) ->setTextBody(’Plain text content’) ->send(); Note: each ‘mailer’ extension comes in 2 major classes: ‘Mailer’ and ‘Message’. ‘Mailer’ always knows the class name and specific of the ‘Message’. Do not attempt ot instantiate ‘Message’ object directly - always use compose() method for it. You may also send several messages at once: $messages = []; foreach ($users as $user) { $messages[] = Yii::$app->mailer->compose() // ... ->setTo($user->email); } Yii::$app->mailer->sendMultiple($messages); Some particular mail extensions may benefit from this approach, using single network message etc. 14.6.3 Composing mail content Yii allows composition of the actual mail messages content via special view files. By default these files should be located at ‘@app/mail’ path. Example mail view file content: <?php use yiihelpersHtml; use yiihelpersUrl; /* @var $this yiiwebView view component instance */ /* @var $message yiimailBaseMessage instance of newly created mail message */ ?> <h2>This message allows you to visit out site home page by one click</h2> <?= Html::a(’Go to home page’, Url::home(’http’)) ?> In order to compose message content via view file simply pass view name to the compose() method: Yii::$app->mailer->compose(’home-link’) // message body becomes a view rendering result here ->setFrom(’[email protected]’) ->setTo(’[email protected]’) ->setSubject(’Message subject’) ->send();
  • 436. 430 CHAPTER 14. SPECIAL TOPICS You may pass additional view parameters to compose() method, which will be available inside the view files: Yii::$app->mailer->compose(’greetings’, [ ’user’ => Yii::$app->user->identity, ’advertisement’ => $adContent, ]); You can specify different view files for HTML and plain text message con- tents: Yii::$app->mailer->compose([ ’html’ => ’contact-html’, ’text’ => ’contact-text’, ]); If you specify view name as a scalar string, its rendering result will be used as HTML body, while plain text body will be composed by removing all HTML entities from HTML one. View rendering result can be wrapped into the layout, which an be setup using yiimailBaseMailer::htmlLayout and yiimailBaseMailer::textLayout. It will work the same way like layouts in regular web application. Layout can be used to setup mail CSS styles or other shared content: <?php use yiihelpersHtml; /* @var $this yiiwebView view component instance */ /* @var $message yiimailMessageInterface the message being composed */ /* @var $content string main view render result */ ?> <?php $this->beginPage() ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/ TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=<?= Yii:: $app->charset ?>" /> <style type="text/css"> .heading {...} .list {...} .footer {...} </style> <?php $this->head() ?> </head> <body> <?php $this->beginBody() ?> <?= $content ?> <div class="footer">With kind regards, <?= Yii::$app->name ?> team</div> <?php $this->endBody() ?> </body> </html> <?php $this->endPage() ?>
  • 437. 14.6. MAILING 431 14.6.4 File attachment You can add attachments to message using methods attach() and attachContent (): $message = Yii::$app->mailer->compose(); // Attach file from local file system: $message->attach(’/path/to/source/file.pdf’); // Create attachment on-the-fly $message->attachContent(’Attachment content’, [’fileName’ => ’attach.txt’, ’ contentType’ => ’text/plain’]); 14.6.5 Embed images You can embed images into the message content using embed() method. This method returns the attachment id, which should be then used at ‘img’ tag. This method is easy to use when composing message content via view file: Yii::$app->mailer->compose(’embed-email’, [’imageFileName’ => ’/path/to/ image.jpg’]) // ... ->send(); Then inside view file you can use following code: <img src="<?= $message->embed($imageFileName); ?>"> 14.6.6 Testing and debugging Developer often a to check, what actual emails are sent by application, what was their content and so on. Such ability is granted by Yii via yiimail BaseMailer::useFileTransport. If enabled, this option enforces saving mail message data into the local files instead of regular sending. These files will be saved under yiimailBaseMailer::fileTransportPath, which is ‘@runtime/- mail’ by default. Note: you can either save messages to the file or send them to actual recipients, but can not do both simultaneously. Mail message file can be opened by regular text file editor, so you can browse actual message headers, content and so on. This mechanism amy prove itself, while debugging application or running unit test. Note: mail message file content is composed via yiimailMessageInterface ::toString(), so it depends on actual mail extension you are using in your application.
  • 438. 432 CHAPTER 14. SPECIAL TOPICS 14.6.7 Creating your own mail solution In order to create your own custom mail solution, you need to create 2 classes: one for the ‘Mailer’ and another one for the ‘Message’. You can use yiimailBaseMailer and yiimailBaseMessage as a base classes for your solution. These classes already contains basic logic, which is described in this guide. However, their usage is not mandatory, it is enough to implement yiimailMailerInterface and yiimailMessageInterface interfaces. Then you need to implement all abstract methods to build you solution. 14.7 Performance Tuning Note: This section is under development. The performance of your web application is based upon two parts. First is the framework performance and the second is the application itself. Yii has a pretty low performance impact on your application out of the box and can be fine-tuned further for production environment. As for the application, we’ll provide some of the best practices along with examples on how to apply them to Yii. 14.7.1 Preparing environment A well configured environment to run PHP application really matters. In order to get maximum performance: • Always use latest stable PHP version. Each major release brings sig- nificant performance improvements and reduced memory usage. • Use APC21 for PHP 5.4 and less or Opcache22 for PHP 5.5 and more. It gives a very good performance boost. 14.7.2 Preparing framework for production Disabling Debug Mode First thing you should do before deploying your application to production environment is to disable debug mode. A Yii application runs in debug mode if the constant YII_DEBUG is defined as true in index.php so to disable debug the following should be in your index.php: defined(’YII_DEBUG’) or define(’YII_DEBUG’, false); 21 http://ru2.php.net/apc 22 http://php.net/opcache
  • 439. 14.7. PERFORMANCE TUNING 433 Debug mode is very useful during development stage, but it would impact performance because some components cause extra burden in debug mode. For example, the message logger may record additional debug information for every message being logged. Enabling PHP opcode cache Enabling the PHP opcode cache improves any PHP application performance and lowers memory usage significantly. Yii is no exception. It was tested with both PHP 5.5 OPcache23 and APC PHP extension24. Both cache and optimize PHP intermediate code and avoid the time spent in parsing PHP scripts for every incoming request. Turning on ActiveRecord database schema caching If the application is using Active Record, we should turn on the schema caching to save the time of parsing database schema. This can be done by setting the Connection::enableSchemaCache property to be true via application configuration protected/config/main.php: return [ // ... ’components’ => [ // ... ’db’ => [ ’class’ => ’yiidbConnection’, ’dsn’ => ’mysql:host=localhost;dbname=mydatabase’, ’username’ => ’root’, ’password’ => ’’, ’enableSchemaCache’ => true, // Duration of schema cache. // ’schemaCacheDuration’ => 3600, // Name of the cache component used. Default is ’cache’. //’schemaCache’ => ’cache’, ], ’cache’ => [ ’class’ => ’yiicachingFileCache’, ], ], ]; Note that cache application component should be configured. 23 http://php.net/manual/en/book.opcache.php 24 http://php.net/manual/en/book.apc.php
  • 440. 434 CHAPTER 14. SPECIAL TOPICS Combining and Minimizing Assets It is possible to combine and minimize assets, typically JavaScript and CSS, in order to slightly improve page load time and therefore deliver better ex- perience for end user of your application. In order to learn how it can be achieved, refer to assets guide section. Using better storage for sessions By default PHP uses files to handle sessions. It is OK for development and small projects but when it comes to handling concurrent requests it’s better to switch to another storage such as database. You can do so by configuring your application via protected/config/main.php: return [ // ... ’components’ => [ ’session’ => [ ’class’ => ’yiiwebDbSession’, // Set the following if want to use DB component other than // default ’db’. // ’db’ => ’mydb’, // To override default session table set the following // ’sessionTable’ => ’my_session’, ], ], ]; You can use CacheSession to store sessions using cache. Note that some cache storage such as memcached has no guarantee that session data will not be lost leading to unexpected logouts. If you have Redis25 on your server, it’s highly recommended as session storage. 14.7.3 Improving application Using Serverside Caching Techniques As described in the Caching section, Yii provides several caching solutions that may improve the performance of a Web application significantly. If the generation of some data takes long time, we can use the data caching ap- proach to reduce the data generation frequency; If a portion of page remains relatively static, we can use the fragment caching approach to reduce its rendering frequency; If a whole page remains relative static, we can use the page caching approach to save the rendering cost for the whole page. 25 http://redis.io/
  • 441. 14.7. PERFORMANCE TUNING 435 Leveraging HTTP to save processing time and bandwidth Leveraging HTTP caching saves both processing time, bandwidth and re- sources significantly. It can be implemented by sending either ETag or Last- Modified header in your application response. If browser is implemented ac- cording to HTTP specification (most browsers are), content will be fetched only if it is different from what it was prevously. Forming proper headers is time consuming task so Yii provides a shortcut in form of controller filter yiifiltersHttpCache. Using it is very easy. In a controller you need to implement behaviors method like the following: public function behaviors() { return [ ’httpCache’ => [ ’class’ => yiifiltersHttpCache::className(), ’only’ => [’list’], ’lastModified’ => function ($action, $params) { $q = new Query(); return strtotime($q->from(’users’)->max(’updated_timestamp’) ); }, // ’etagSeed’ => function ($action, $params) { // return // generate etag seed here //} ], ]; } In the code above one can use either etagSeed or lastModified. Implementing both isn’t necessary. The goal is to determine if content was modified in a way that is cheaper than fetching and rendering that content. lastModified should return unix timestamp of the last content modification while etagSeed should return a string that is then used to generate ETag header value. Database Optimization Fetching data from database is often the main performance bottleneck in a Web application. Although using caching may alleviate the performance hit, it does not fully solve the problem. When the database contains enorm- ous data and the cached data is invalid, fetching the latest data could be prohibitively expensive without proper database and query design. Design index wisely in a database. Indexing can make SELECT queries much faster, but it may slow down INSERT, UPDATE or DELETE queries. For complex queries, it is recommended to create a database view for it instead of issuing the queries inside the PHP code and asking DBMS to parse them repetitively. Do not overuse Active Record. Although Active Record is good at mod- eling data in an OOP fashion, it actually degrades performance due to the
  • 442. 436 CHAPTER 14. SPECIAL TOPICS fact that it needs to create one or several objects to represent each row of query result. For data intensive applications, using DAO or database APIs at lower level could be a better choice. Last but not least, use LIMIT in your SELECT queries. This avoids fetching overwhelming data from database and exhausting the memory allocated to PHP. Using asArray A good way to save memory and processing time on read-only pages is to use ActiveRecord’s asArray method. class PostController extends Controller { public function actionIndex() { $posts = Post::find()->orderBy(’id DESC’)->limit(100)->asArray()-> all(); return $this->render(’index’, [’posts’ => $posts]); } } In the view you should access fields of each individual record from $posts as array: foreach ($posts as $post) { echo $post[’title’]."<br>"; } Note that you can use array notation even if asArray wasn’t specified and you’re working with AR objects. Processing data in background In order to respond to user requests faster you can process heavy parts of the request later if there’s no need for immediate response. • Cron jobs + console. • queues + handlers. TBD If nothing helps If nothing helps, never assume what may fix performance problem. Always profile your code instead before changing anything. The following tools may be helpful: • Yii debug toolbar and debugger
  • 443. 14.7. PERFORMANCE TUNING 437 • XDebug profiler26 • XHProf27 26 http://xdebug.org/docs/profiler 27 http://www.php.net/manual/en/book.xhprof.php
  • 444. 438 CHAPTER 14. SPECIAL TOPICS Error: not existing file: tutorial-shared-hosting.md
  • 445. 14.8. USING TEMPLATE ENGINES 439 14.8 Using template engines Note: This section is under development. By default, Yii uses PHP as its template language, but you can configure Yii to support other rendering engines, such as Twig28 or Smarty29. The view component is responsible for rendering views. You can add a custom template engine by reconfiguring this component’s behavior: [ ’components’ => [ ’view’ => [ ’class’ => ’yiiwebView’, ’renderers’ => [ ’tpl’ => [ ’class’ => ’yiismartyViewRenderer’, //’cachePath’ => ’@runtime/Smarty/cache’, ], ’twig’ => [ ’class’ => ’yiitwigViewRenderer’, //’cachePath’ => ’@runtime/Twig/cache’, //’options’ => [], /* Array of twig options */ ’globals’ => [’html’ => ’yiihelpersHtml’], ’uses’ => [’yiibootstrap’], ], // ... ], ], ], ] In the code above, both Smarty and Twig are configured to be useable by the view files. But in order to get these extensions into your project, you need to also modify your composer.json file to include them, too: "yiisoft/yii2-smarty": "*", "yiisoft/yii2-twig": "*", That code would be added to the require section of composer.json. After making that change and saving the file, you can install the extensions by running composer update --prefer-dist in the command-line. 14.8.1 Twig To use Twig, you need to create templates in files that have the .twig exten- sion (or use another file extension but configure the component accordingly). Unlike standard view files, when using Twig you must include the extension in your $this->render() controller call: return $this->render(’renderer.twig’, [’username’ => ’Alex’]); 28 http://twig.sensiolabs.org/ 29 http://www.smarty.net/
  • 446. 440 CHAPTER 14. SPECIAL TOPICS Template syntax The best resource to learn Twig basics is its official documentation you can find at twig.sensiolabs.org30. Additionally there are Yii-specific syntax ex- tensions described below. Method and function calls If you need result you can call a method or a function using the following syntax: {% set result = my_function({’a’ : ’b’}) %} {% set result = myObject.my_function({’a’ : ’b’}) %} If you need to echo result instead of assigning it to a variable: {{ my_function({’a’ : ’b’}) }} {{ myObject.my_function({’a’ : ’b’}) }} In case you don’t need result you shoud use void wrapper: {{ void(my_function({’a’ : ’b’})) }} {{ void(myObject.my_function({’a’ : ’b’})) }} Setting object properties There’s a special function called set that al- lows you to set property of an object. For example, the following in the template will change page title: {{ set(this, ’title’, ’New title’) }} Importing namespaces and classes You can import additional classes and namespaces right in the template: Namespace import: {{ use(’/app/widgets’) }} Class import: {{ use(’/yii/widgets/ActiveForm’) }} Aliased class import: {{ use({’alias’ => ’/app/widgets/MyWidget’}) }} Referencing other templates There are two ways of referencing tem- plates in include and extends statements: {% include "comment.twig" %} {% extends "post.twig" %} {% include "@app/views/snippets/avatar.twig" %} {% extends "@app/views/layouts/2columns.twig" %} 30 http://twig.sensiolabs.org/documentation
  • 447. 14.8. USING TEMPLATE ENGINES 441 In the first case the view will be searched relatively to the current template path. For comment.twig and post.twig that means these will be searched in the same directory as the currently rendered template. In the second case we’re using path aliases. All the Yii aliases such as @app are available by default. Widgets Extension helps using widgets in convenient way converting their syntax to function calls: {{ use(’yii/bootstrap’) }} {{ nav_bar_begin({ ’brandLabel’: ’My Company’, }) }} {{ nav_widget({ ’options’: { ’class’: ’navbar-nav navbar-right’, }, ’items’: [{ ’label’: ’Home’, ’url’: ’/site/index’, }] }) }} {{ nav_bar_end() }} In the template above nav_bar_begin, nav_bar_end or nav_widget consists of two parts. First part is widget name coverted to lowercase and underscores: NavBar becomes nav_bar, Nav becomes nav. _begin, _end and _widget are the same as ::begin(), ::end() and ::widget() calls of a widget. One could also use more generic widget_end() that executes Widget::end(). Assets Assets could be registered the following way: {{ use(’yii/web/JqueryAsset’) }} {{ register_jquery_asset() }} In the call above register identifies that we’re working with assets while jquery_asset translates to JqueryAsset class that we’ve already imported with use. Forms You can build forms the following way: {{ use(’yii/widgets/ActiveForm’) }} {% set form = active_form_begin({ ’id’ : ’login-form’, ’options’ : {’class’ : ’form-horizontal’}, }) %} {{ form.field(model, ’username’) | raw }} {{ form.field(model, ’password’).passwordInput() | raw }} <div class="form-group"> <input type="submit" value="Login" class="btn btn-primary" />
  • 448. 442 CHAPTER 14. SPECIAL TOPICS </div> {{ active_form_end() }} URLs There are two functions you can use for building URLs: <a href="{{ path(’blog/view’, {’alias’ : post.alias}) }}">{{ post.title }}</ a> <a href="{{ url(’blog/view’, {’alias’ : post.alias}) }}">{{ post.title }}</a > path generates relative URL while url generates absolute one. Internally both are using yiihelpersUrl. Additional variables Within Twig templates the following variables are always defined: • app, which equates to Yii::$app • this, which equates to the current View object Additional configuration Yii Twig extension allows you to define your own syntax and bring regular helper classes into templates. Let’s review configuration options. Globals You can add global helpers or values via the application config- uration’s globals variable. You can define both Yii helpers and your own variables there: ’globals’ => [ ’html’ => ’yiihelpersHtml’, ’name’ => ’Carsten’, ’GridView’ => ’yiigridGridView’, ], Once configured, in your template you can use the globals in the following way: Hello, {{name}}! {{ html.a(’Please login’, ’site/login’) | raw }}. {{ GridView.widget({’dataProvider’ : provider}) | raw }} Functions You can define additional functions like the following: ’functions’ => [ ’rot13’ => ’str_rot13’, ’truncate’ => ’yiihelpersStringHelper::truncate’, ], In template they could be used like the following: ‘{{ rot13(’test’) }}‘ ‘{{ truncate(post.text, 100) }}‘
  • 449. 14.8. USING TEMPLATE ENGINES 443 Filters Additional filters may be added via the application configuration’s filters option: ’filters’ => [ ’jsonEncode’ => ’yiihelpersJson::encode’, ], Then in the template you can apply filter using the following syntax: {{ model|jsonEncode }} 14.8.2 Smarty To use Smarty, you need to create templates in files that have the .tpl exten- sion (or use another file extension but configure the component accordingly). Unlike standard view files, when using Smarty you must include the exten- sion in your $this->render() or $this->renderPartial() controller calls: return $this->render(’renderer.tpl’, [’username’ => ’Alex’]); Template syntax The best resource to learn Smarty template syntax is its official document- ation you can find at www.smarty.net31. Additionally there are Yii-specific syntax extensions described below. Setting object properties There’s a special function called set that al- lows you to set common properties of the view and controller. Currently available properties are title, theme and layout: {set title="My Page"} {set theme="frontend"} {set layout="main.tpl"} For title there’s dedicated block as well: {title}My Page{/title} Setting meta tags Meta tags could be set like to following: {meta keywords="Yii,PHP,Smarty,framework"} There’s also dedicated block for description: {description}This is my page about Smarty extension{/description} Calling object methods Sometimes you need calling 31 http://www.smarty.net/docs/en/
  • 450. 444 CHAPTER 14. SPECIAL TOPICS Importing static classes, using widgets as functions and blocks You can import additional static classes right in the template: {use class="yiihelpersHtml"} {Html::mailto(’[email protected]’)} If you want you can set custom alias: {use class="yiihelpersHtml" as="Markup"} {Markup::mailto(’[email protected]’)} Extension helps using widgets in convenient way converting their syntax to function calls or blocks. For regular widgets function could be used like the following: {use class=’@yiigridGridView’ type=’function’} {GridView dataProvider=$provider} For widgets with begin and end methods such as ActiveForm it’s better to use block: {use class=’yiiwidgetsActiveForm’ type=’block’} {ActiveForm assign=’form’ id=’login-form’ action=’/form-handler’ options=[’ class’ => ’form-horizontal’]} {$form->field($model, ’firstName’)} <div class="form-group"> <div class="col-lg-offset-1 col-lg-11"> <input type="submit" value="Login" class="btn btn-primary" /> </div> </div> {/ActiveForm} If you’re using particular widget a lot, it is a good idea to declare it in application config and remove {use class call from templates: ’components’ => [ ’view’ => [ // ... ’renderers’ => [ ’tpl’ => [ ’class’ => ’yiismartyViewRenderer’, ’widgets’ => [ ’blocks’ => [ ’ActiveForm’ => ’yiiwidgetsActiveForm’, ], ], ], ], ], ], Referencing other templates There are two main ways of referencing templates in include and extends statements:
  • 451. 14.8. USING TEMPLATE ENGINES 445 {include ’comment.tpl’} {extends ’post.tpl’} {include ’@app/views/snippets/avatar.tpl’} {extends ’@app/views/layouts/2columns.tpl’} In the first case the view will be searched relatively to the current template path. For comment.tpl and post.tpl that means these will be searched in the same directory as the currently rendered template. In the second case we’re using path aliases. All the Yii aliases such as @app are available by default. CSS, JavaScript and asset bundles In order to register JavaScript and CSS files the following syntax could be used: {registerJsFile url=’http://maps.google.com/maps/api/js?sensor=false’ position=’POS_END’} {registerCssFile url=’@assets/css/normalizer.css’} If you need JavaScript and CSS directly in the template there are convenient blocks: {registerJs key=’show’ position=’POS_LOAD’} $("span.show").replaceWith(’<div class="show">’); {/registerJs} {registerCss} div.header { background-color: #3366bd; color: white; } {/registerCss} Asset bundles could be registered the following way: {use class="yiiwebJqueryAsset"} {JqueryAsset::register($this)|void} Here we’re using void modifier because we don’t need method call result. URLs There are two functions you can use for building URLs: <a href="{path route=’blog/view’ alias=$post.alias}">{$post.title}</a> <a href="{url route=’blog/view’ alias=$post.alias}">{$post.title}</a> path generates relative URL while url generates absolute one. Internally both are using yiihelpersUrl. Additional variables Within Smarty templates the following variables are always defined: • $app, which equates to Yii::$app • $this, which equates to the current View object
  • 452. 446 CHAPTER 14. SPECIAL TOPICS Accessing config params Yii parameters that are available in your ap- plication through Yii::$app->params->something could be used the following way: ‘{#something#}‘ 14.9 Working with Third-Party Code From time to time, you may need to use some third-party code in your Yii applications. Or you may want to use Yii as a library in some third-party systems. In this section, we will show how to achieve these goals. 14.9.1 Using Third-Party Libraries in Yii To use a third-party library in a Yii application, you mainly need to make sure the classes in the library are properly included or can be autoloaded. Using Composer Packages Many third-party libraries are released in terms of Composer32 packages. You can install such libraries by taking the following two simple steps: 1. modify the composer.json file of your application and specify which Composer packages you want to install. 2. run php composer.phar install to install the specified packages. The classes in the installed Composer packages can be autoloaded using the Composer autoloader. Make sure the entry script of your application contains the following lines to install the Composer autoloader: // install Composer autoloader require(__DIR__ . ’/../vendor/autoload.php’); // include Yii class file require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’); Using Downloaded Libraries If a library is not released as a Composer package, you should follow its installation instructions to install it. In most cases, you will need to download a release file manually and unpack it in the BasePath/vendor directory, where BasePath represents the base path of your application. If a library carries its own class autoloader, you may install it in the entry script of your application. It is recommended the installation is done 32 https://getcomposer.org/
  • 453. 14.9. WORKING WITH THIRD-PARTY CODE 447 before you include the Yii.php file so that the Yii class autoloader can take precedence in autoloading classes. If a library does not provide a class autoloader, but its class naming fol- lows PSR-433, you may use the Yii class autoloader to autoload the classes. All you need to do is just to declare a root alias for each root namespace used in its classes. For example, assume you have installed a library in the direct- ory vendor/foo/bar, and the library classes are under the xyz root namespace. You can include the following code in your application configuration: [ ’aliases’ => [ ’@xyz’ => ’@vendor/foo/bar’, ], ] If neither of the above is the case, it is likely that the library relies on PHP include path configuration to correctly locate and include class files. Simply follow its instruction on how to configure the PHP include path. In the worst case when the library requires explicitly including every class file, you can use the following method to include the classes on demand: • Identify which classes the library contains. • List the classes and the corresponding file paths in Yii::$classMap in the entry script of the application. For example, Yii::$classMap[’Class1’] = ’path/to/Class1.php’; Yii::$classMap[’Class2’] = ’path/to/Class2.php’; 14.9.2 Using Yii in Third-Party Systems Because Yii provides many excellent features, sometimes you may want to use some of its features to support developing or enhancing 3rd-party sys- tems, such as WordPress, Joomla, or applications developed using other PHP frameworks. For example, you may want to use the yiihelpers ArrayHelper class or use the Active Record feature in a third-party sys- tem. To achieve this goal, you mainly need to take two steps: install Yii, and bootstrap Yii. If the third-party system uses Composer to manage its dependencies, you can simply run the following commands to install Yii: php composer.phar require yiisoft/yii2-framework:* php composer.phar install Otherwise, you can download34 the Yii release file and unpack it in the BasePath/vendor directory. 33 http://www.php-fig.org/psr/psr-4/ 34 http://www.yiiframework.com/download/
  • 454. 448 CHAPTER 14. SPECIAL TOPICS Next, you should modify the entry script of the 3rd-party system by including the following code at the beginning: require(__DIR__ . ’/../vendor/yiisoft/yii2/Yii.php’); $yiiConfig = require(__DIR__ . ’/../config/yii/web.php’); new yiiwebApplication($yiiConfig); // Do NOT call run() here As you can see, the code above is very similar to that in the entry script of a typical Yii application. The only difference is that after the application instance is created, the run() method is not called. This is because by calling run(), Yii will take over the control of the request handling workflow. Like in a Yii application, you should configure the application instance based on the environment running the third-party system. For example, to use the Active Record feature, you need to configure the db application component with the DB connection setting used by the third-party system. Now you can use most features provided by Yii. For example, you can create Active Record classes and use them to work with databases. 14.9.3 Using Yii 2 with Yii 1 If you were using Yii 1 previously, it is likely you have a running Yii 1 application. Instead of rewriting the whole application in Yii 2, you may just want to enhance it using some of the features only available in Yii 2. This can be achieved as described below. Note: Yii 2 requires PHP 5.4 or above. You should make sure that both your server and the existing application support this. First, install Yii 2 in your existing application by following the instructions given in the last subsection. Second, modify the entry script of the application as follows, // include the customized Yii class described below require(__DIR__ . ’/../components/Yii.php’); // configuration for Yii 2 application $yii2Config = require(__DIR__ . ’/../config/yii2/web.php’); new yiiwebApplication($yii2Config); // Do NOT call run() // configuration for Yii 1 application $yii1Config = require(__DIR__ . ’/../config/yii1/main.php’); Yii::createWebApplication($yii1Config)->run(); Because both Yii 1 and Yii 2 have the Yii class, you should create a custom- ized version to combine them. The above code includes the customized Yii class file, which can be created as follows. $yii2path = ’/path/to/yii2’; require($yii2path . ’/BaseYii.php’); // Yii 2.x
  • 455. 14.9. WORKING WITH THIRD-PARTY CODE 449 $yii1path = ’/path/to/yii1’; require($yii1path . ’/YiiBase.php’); // Yii 1.x class Yii extends yiiBaseYii { // copy-paste the code in YiiBase (1.x) here } Yii::$classMap = include($yii2path . ’/classes.php’); // register Yii2 autoloader via Yii1 Yii::registerAutoloader([’Yii’, ’autoload’]); That’s all! Now in any part of your code, you can use Yii::$app to access the Yii 2 application instance, while Yii::app() will give you the Yii 1 application instance: echo get_class(Yii::app()); // outputs ’CWebApplication’ echo get_class(Yii::$app); // outputs ’yiiwebApplication’
  • 456. 450 CHAPTER 14. SPECIAL TOPICS
  • 457. Chapter 15 Widgets 15.1 Bootstrap Widgets Note: This section is under development. Out of the box, Yii includes support for the Bootstrap 31 markup and com- ponents framework (also known as “Twitter Bootstrap”). Bootstrap is an excellent, responsive framework that can greatly speed up the client-side of your development process. The core of Bootstrap is represented by two parts: • CSS basics, such as a grid layout system, typography, helper classes, and responsive utilities. • Ready to use components, such as forms, menus, pagination, modal boxes, tabs etc. 15.1.1 Basics Yii doesn’t wrap the bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details about using the basics at bootstrap documentation website2. Still Yii provides a convenient way to include bootstrap assets in your pages with a single line added to AppAsset.php located in your @app/assets directory: public $depends = [ ’yiiwebYiiAsset’, ’yiibootstrapBootstrapAsset’, // this line ]; Using bootstrap through Yii asset manager allows you to minimize its re- sources and combine with your own resources when needed. 1 http://getbootstrap.com/ 2 http://getbootstrap.com/css/ 451
  • 458. 452 CHAPTER 15. WIDGETS 15.1.2 Yii widgets Most complex bootstrap components are wrapped into Yii widgets to al- low more robust syntax and integrate with framework features. All widgets belong to yiibootstrap namespace: • yiibootstrapActiveForm • yiibootstrapAlert • yiibootstrapButton • yiibootstrapButtonDropdown • yiibootstrapButtonGroup • yiibootstrapCarousel • yiibootstrapCollapse • yiibootstrapDropdown • yiibootstrapModal • yiibootstrapNav • yiibootstrapNavBar • yiibootstrapProgress • yiibootstrapTabs 15.1.3 Using the .less files of Bootstrap directly If you want to include the Bootstrap css directly in your less files3 you may need to disable the original bootstrap css files to be loaded. You can do this by setting the css property of the yiibootstrapBootstrapAsset to be empty. For this you need to configure the assetManager application component as follows: ’assetManager’ => [ ’bundles’ => [ ’yiibootstrapBootstrapAsset’ => [ ’css’ => [], ] ] ] 3 http://getbootstrap.com/getting-started/#customizing
  • 459. 15.2. JQUERY UI WIDGETS 453 15.2 Jquery UI Widgets Note: This section is under development. Out of the box, Yii includes support for the jQuery UI4 library. jQuery UI is a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library. 15.2.1 Yii widgets Most complex jQuery UI components are wrapped into Yii widgets to al- low more robust syntax and integrate with framework features. All widgets belong to yiijui namespace: • yiijuiAccordion • yiijuiAutoComplete • yiijuiDatePicker • yiijuiDialog • yiijuiDraggable • yiijuiDroppable • yiijuiMenu • yiijuiProgressBar • yiijuiResizable • yiijuiSelectable • yiijuiSlider • yiijuiSliderInput • yiijuiSortable • yiijuiSpinner • yiijuiTabs 4 http://api.jqueryui.com/
  • 460. 454 CHAPTER 15. WIDGETS
  • 461. Chapter 16 Helpers 16.1 Helpers Note: This section is under development. Yii provides many classes that help simplify common coding tasks, such as string or array manipulations, HTML code generation, and so on. These helper classes are organized under the yiihelpers namespace and are all static classes (meaning they contain only static properties and methods and should not be instantiated). You use a helper class by directly calling one of its static methods, like the following: use yiihelpersHtml; echo Html::encode(’Test > test’); Note: To support extending helper classes, Yii breaks each core helper class into two classes: a base class (e.g. BaseArrayHelper) and a concrete class (e.g. ArrayHelper). When you use a helper, you should only use the concrete version and never use the base class. 16.1.1 Core Helper Classes The following core helper classes are provided in the Yii releases: • ArrayHelper • Console • FileHelper • Html 455
  • 462. 456 CHAPTER 16. HELPERS • HtmlPurifier • Image • Inflector • Json • Markdown • Security • StringHelper • Url • VarDumper 16.1.2 Extending Helper Classes To custom a core helper class (e.g. yiihelpersArrayHelper), you should extend from its corresponding base class (e.g. yiihelpersBaseArrayHelper ) and name your class the same as the corresponding concrete class (e.g. yiihelpersArrayHelper), including its namespace. The following example shows how to customize the yiihelpersArrayHelper ::merge() method of the yiihelpersArrayHelper class: namespace yiihelpers; use yiihelpersBaseArrayHelper; class ArrayHelper extends BaseArrayHelper { public static function merge($a, $b) { // your custom implementation } } Save your class in a file named ArrayHelper.php. The file can be in any directory, such as @app/components. Next, in your application’s entry script, add the following line of code after including the yii.php file: Yii::$classMap[’yiihelpersArrayHelper’] = ’path/to/ArrayHelper.php’; The above line instructs the Yii class autoloader to load your version of the helper class, instead of the one included in the Yii releases.
  • 463. 16.1. HELPERS 457 Error: not existing file: helper-array.md
  • 464. 458 CHAPTER 16. HELPERS Error: not existing file: helper-html.md
  • 465. 16.1. HELPERS 459 Error: not existing file: helper-url.md
  • 466. 460 CHAPTER 16. HELPERS Error: not existing file: helper-security.md