The Denitive Guide
to
Yii 2.0
Qiang Xue,
Alexander Makarov,
Carsten Brandt,
Klimov Paul,
and
the Yii community
Copyright 2014 Yii Software LLC.
Contents
1 Introduction
1
1.1
What is Yii
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Upgrading from Version 1.1
. . . . . . . . . . . . . . . . . . .
2 Getting Started
1
2
13
2.1
Installing Yii
2.2
Running Applications
. . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3
Saying Hello . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
2.4
Working with Forms
26
2.5
Working with Databases . . . . . . . . . . . . . . . . . . . . .
32
2.6
Generating Code with Gii
. . . . . . . . . . . . . . . . . . . .
37
2.7
Looking Ahead
. . . . . . . . . . . . . . . . . . . . . . . . . .
44
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . .
3 Application Structure
13
19
47
3.1
Overview
3.2
Entry Scripts
. . . . . . . . . . . . . . . . . . . . . . . . . . .
48
3.3
Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
3.4
Application Components . . . . . . . . . . . . . . . . . . . . .
62
3.5
Controllers
65
3.6
Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
3.7
Views
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
84
3.8
Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
99
3.9
Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
3.10 Widgets
3.11 Assets
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
3.12 Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
4 Handling Requests
143
4.1
Overview
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
4.2
Bootstrapping . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
4.3
Routing and URL Creation
4.4
Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
4.5
Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
. . . . . . . . . . . . . . . . . . . 145
iii
iv
CONTENTS
4.6
Sessions and Cookies . . . . . . . . . . . . . . . . . . . . . . . 167
4.7
Handling Errors . . . . . . . . . . . . . . . . . . . . . . . . . . 174
4.8
Logging
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
5 Key Concepts
187
5.1
Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
5.2
Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
5.3
Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
5.4
Behaviors
5.5
Congurations
5.6
Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
5.7
Class Autoloading
5.8
Service Locator . . . . . . . . . . . . . . . . . . . . . . . . . . 212
5.9
Dependency Injection Container . . . . . . . . . . . . . . . . . 215
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
. . . . . . . . . . . . . . . . . . . . . . . . . . 203
. . . . . . . . . . . . . . . . . . . . . . . . 210
6 Working with Databases
223
6.1
Database Access Objects . . . . . . . . . . . . . . . . . . . . . 223
6.2
Query Builder . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
6.3
Active Record . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
6.4
Database Migration . . . . . . . . . . . . . . . . . . . . . . . . 273
7 Getting Data from Users
287
7.1
Creating Forms . . . . . . . . . . . . . . . . . . . . . . . . . . 287
7.2
Validating Input
7.3
Uploading Files . . . . . . . . . . . . . . . . . . . . . . . . . . 302
7.4
Collecting tabular input
7.5
Complex Forms with Multiple Models
8 Displaying Data
. . . . . . . . . . . . . . . . . . . . . . . . . 289
. . . . . . . . . . . . . . . . . . . . . 307
. . . . . . . . . . . . . 309
311
8.1
Data Formatter . . . . . . . . . . . . . . . . . . . . . . . . . . 311
8.2
Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
8.3
Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
8.4
Data providers
8.5
Data widgets
8.6
Working with Client Scripts . . . . . . . . . . . . . . . . . . . 333
8.7
Theming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
9 Security
. . . . . . . . . . . . . . . . . . . . . . . . . . 317
. . . . . . . . . . . . . . . . . . . . . . . . . . . 322
339
9.1
Authentication
. . . . . . . . . . . . . . . . . . . . . . . . . . 339
9.2
Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
9.3
Working with Passwords . . . . . . . . . . . . . . . . . . . . . 355
9.4
Security best practices . . . . . . . . . . . . . . . . . . . . . . 359
CONTENTS
v
10 Caching
10.1 Caching
363
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
10.2 Data Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
10.3 Fragment Caching
. . . . . . . . . . . . . . . . . . . . . . . . 371
10.4 Page Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
10.5 HTTP Caching . . . . . . . . . . . . . . . . . . . . . . . . . . 375
11 RESTful Web Services
11.1 Quick Start
11.2 Resources
11.3 Controllers
11.4 Routing
379
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
11.5 Response Formatting . . . . . . . . . . . . . . . . . . . . . . . 392
11.6 Authentication
. . . . . . . . . . . . . . . . . . . . . . . . . . 395
11.7 Rate Limiting . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
11.8 Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
11.9 Error Handling
. . . . . . . . . . . . . . . . . . . . . . . . . . 401
12 Development Tools
405
13 Testing
409
13.1 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
13.2 Testing environment setup . . . . . . . . . . . . . . . . . . . . 411
13.3 Unit Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
13.4 Functional Tests
. . . . . . . . . . . . . . . . . . . . . . . . . 412
13.5 Acceptance Tests . . . . . . . . . . . . . . . . . . . . . . . . . 412
13.6 Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
13.7 Managing Fixtures
. . . . . . . . . . . . . . . . . . . . . . . . 418
14 Special Topics
421
14.1 Creating your own Application structure . . . . . . . . . . . . 423
14.2 Console applications
. . . . . . . . . . . . . . . . . . . . . . . 424
14.3 Core Validators . . . . . . . . . . . . . . . . . . . . . . . . . . 429
14.4 Internationalization . . . . . . . . . . . . . . . . . . . . . . . . 440
14.5 Mailing
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
14.6 Performance Tuning
. . . . . . . . . . . . . . . . . . . . . . . 456
14.7 Shared Hosting Environment
. . . . . . . . . . . . . . . . . . 461
14.8 Using template engines . . . . . . . . . . . . . . . . . . . . . . 463
14.9 Working with Third-Party Code . . . . . . . . . . . . . . . . . 464
15 Widgets
469
vi
CONTENTS
16 Helpers
16.1 Helpers
473
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
16.2 ArrayHelper . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
16.3 Html helper . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
16.4 Url Helper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486
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 especially suitable for developing large-scale applications such as portals, forums, 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 knowing how Yii compares:
•
Like most PHP frameworks, Yii implements the MVC (Model-ViewController) 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-touse features: query builders and ActiveRecord for both relational and
NoSQL databases; RESTful API development support; multi-tier caching
support; and more.
1
2
CHAPTER 1.
•
INTRODUCTION
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.
1
Yii is not a one-man show, it is backed up by a strong core developer team ,
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 exposed 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 complete 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
eorts 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 nd 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 namespaces
2 and traits3 . Understanding these
concepts will help you more easily pick up Yii 2.0.
1.2 Upgrading from Version 1.1
There are many dierences between versions 1.1 and 2.0 of Yii as the framework 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
nd the major dierences between the two versions.
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
1
http://www.yiiframework.com/about/
http://www.php.net/manual/en/language.namespaces.php
3
http://www.php.net/manual/en/language.oop5.traits.php
2
1.2.
UPGRADING FROM VERSION 1.1
3
denitive 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
4
Yii 2.0 fully embraces Composer , the de facto PHP package manager. Installation 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 extensions 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 dierences
on the language level that you should pay attention to. Below is a summary
of the major changes regarding PHP:
•
•
•
5
Namespaces .
6
Anonymous functions .
Short array syntax
[...elements...]
is used instead of
array(...elements
...).
•
Short echo tags
<?=
are used in view les. This is safe to use starting
from PHP 5.4.
•
•
•
•
•
7
SPL classes and interfaces .
8
Late Static Bindings .
9
Date and Time .
10
Traits .
11 . Yii 2.0 makes use of the intl PHP extension to support inter-
intl
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.,
yii\web\Request.
The C prex is no longer
used in class names. The naming scheme now follows the directory structure.
For example,
/Request.php
4
yii\web\Request
indicates that the corresponding class le is
under the Yii framework folder.
https://getcomposer.org/
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
11
http://php.net/manual/en/book.intl.php
5
web
4
CHAPTER 1.
INTRODUCTION
(You can use any core class without explicitly including that class le,
thanks to the Yii class loader.)
1.2.4 Component and Object
Yii 2.0 breaks the
and
CComponent
yii\base\Component.
class in 1.1 into two classes:
The
Object
yii\base\Object
class is a lightweight base class that
allows dening object properties via getters and setters.
class extends from
Object
The
Component
and supports events and behaviors.
If your class does not need the event or behavior feature, you should
consider using
Object
as the base class. This is usually the case for classes
that represent basic data structures.
1.2.5 Object Conguration
Object
The
class introduces a uniform way of conguring objects.
descendant class of
Object
Any
should declare its constructor (if needed) in the
following way so that it can be properly congured:
class MyClass extends \yii\base\Object
{
public function __construct($param1, $param2, $config = [])
{
// ... initialization before configuration is applied
}
parent::__construct($config);
public function init()
{
parent::init();
}
}
// ... initialization after configuration is applied
In the above, the last parameter of the constructor must take a conguration
array that contains name-value pairs for initializing the properties at the end
of the constructor. You can override the
init()
method to do initialization
work that should be done after the conguration has been applied.
By following this convention, you will be able to create and congure
new objects using a conguration array:
$object = Yii::createObject([
'class' => 'MyClass',
'property1' => 'abc',
'property2' => 'cde',
], [$param1, $param2]);
More details about congurations can be found in the Object Congurations
section.
1.2.
UPGRADING FROM VERSION 1.1
5
1.2.6 Events
In Yii 1, events were created by dening an
on-method
(e.g.,
onBeforeSave).
In Yii 2, you can now use any event name. You trigger an event by calling
the
trigger()
method:
$event = new \yii\base\Event;
$component->trigger($eventName, $event);
To attach a handler to an event, use the
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 le/directory paths and
URLs. Yii 2.0 also now requires an alias name to start with the
@
character,
to dierentiate aliases from normal le/directory paths or URLs. For example, the alias
@yii
refers to the Yii installation directory.
Path aliases are
supported in most places in the Yii core code. For example,
yii\caching
\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 recom-
mended that a path alias be dened for each root namespace, thereby allowing you to use Yii class autoloader without any further conguration.
For example, because
yii\web\Request
@yii
refers to the Yii installation directory, a class like
can be autoloaded. If you use a third party library, such as
the Zend Framework, you may dene a path alias
@Zend
that refers to that
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 Aliases section.
1.2.8 Views
The most signicant change about views in Yii 2 is that the special variable
$this
$this
in a view no longer refers to the current controller or widget. Instead,
now refers to a
object is of type
view
object, a new concept introduced in 2.0. The
yii\web\View,
view
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
not
$this->renderPartial().
The call to
render
$this->render(),
also now has to be explicitly
6
CHAPTER 1.
echoed, as the
render()
INTRODUCTION
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 ocial 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 congure the
View::$renderers
view
application component by setting the
property. Please refer to the Template Engines section
for more details.
1.2.9 Models
Yii 2.0 uses
The class
extend
yii\base\Model
CFormModel
CModel
as the base model, similar to
in 1.1.
has been dropped entirely. Instead, in Yii 2 you should
yii\base\Model
to create a form model class.
Yii 2.0 introduces a new method called
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'],
];
}
backend and frontend. For 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.
In the above, two scenarios are declared:
backend
The
scenario, both the
rules()
method is still used to declare the validation rules. Note
that due to the introduction of
scenarios(),
there is no longer an
unsafe
validator.
In most cases, you do not need to override
scenarios()
if the
rules()
method fully species 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
to
yii\web\Controller as the base controller class, which is similar
CController in Yii 1.1. yii\base\Action is the base class for action classes.
The most obvious impact of these changes on your code is that a controller action should return the content that you want to render instead of
echoing it:
1.2.
UPGRADING FROM VERSION 1.1
7
public function actionView($id)
{
$model = \app\models\Post::findOne($id);
if ($model) {
return $this->render('view', ['model' => $model]);
} else {
throw new \yii\web\NotFoundHttpException;
}
}
Please refer to the Controllers section for more details about controllers.
1.2.11 Widgets
Yii 2.0 uses
yii\base\Widget
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
begin(), end(), and widget()
have been introduced, to be used like so:
use yii\widgets\Menu;
use yii\widgets\ActiveForm;
// 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 ...
ActiveForm::end();
Please refer to the Widgets section for more details.
1.2.12 Themes
Themes work completely dierently in 2.0. They are now based on a path
mapping mechanism that maps a source view le path to a themed view
le path. For example, if the path map for a theme is
web/themes/basic'], then the themed version for the
/index.php will be /web/themes/basic/site/index.php.
['/web/views' => '/
/web/views/site
view le
For this reason, themes
can now be applied to any view le, even a view rendered outside of the
context of a controller or a widget.
CThemeManager component. Instead, theme
view application component.
Also, there is no more
gurable property of the
Please refer to the Theming section for more details.
is a con-
8
CHAPTER 1.
INTRODUCTION
1.2.13 Console Applications
Console applications are now organized as controllers, like Web applications.
Console controllers should extend from
to
CConsoleCommand
yii\console\Controller,
similar
in 1.1.
yii <route>,
To run a console command, use
controller route (e.g.
sitemap/index).
where
<route>
stands for a
Additional anonymous arguments are
passed as the parameters to the corresponding controller action method,
while named arguments are parsed according to the declarations in
\console\Controller::options().
yii
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 module
12 .
Message translation is now performed via the
i18n
application compo-
nent. This component manages a set of message sources, which allows you
to use dierent message sources based on message categories.
Please refer to the Internationalization section for more details.
1.2.15 Action Filters
Action lters are implemented via behaviors now. To dene a new, custom
lter, extend from
yii\base\ActionFilter.
To use a lter, attach the lter
class to the controller as a behavior. For example, to use the
\AccessControl
yii\filters
lter, you would have the following code in a controller:
public function behaviors()
{
return [
'access' => [
'class' => 'yii\filters\AccessControl',
'rules' => [
['allow' => true, 'actions' => ['admin'], 'roles' => ['@']],
],
],
];
}
Please refer to the Filtering section for more details.
12
http://pecl.php.net/package/intl
1.2.
UPGRADING FROM VERSION 1.1
9
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 les (e.g. JavaScript les, CSS
les, image les, etc.) within a directory. Each asset bundle is represented
yii\web\AssetBundle. By registering an asset bundle
yii\web\AssetBundle::register(), you make the assets in that bundle
as a class extending
via
accessible via the Web. Unlike in Yii 1, the page registering the bundle will
automatically contain the references to the JavaScript and CSS les specied
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.
•
•
•
•
•
yii\helpers\Html
yii\helpers\ArrayHelper
yii\helpers\StringHelper
yii\helpers\FileHelper
yii\helpers\Json
Please refer to the Helper Overview section for more details.
1.2.18 Forms
Yii 2.0 introduces the
\ActiveForm.
eld
concept for building a form using
yii\widgets
A eld is a container consisting of a label, an input, an error
message, and/or a hint text. A eld is represented as an
ActiveField object.
Using elds, you can build a form more cleanly than before:
<?php $form = yii\widgets\ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Login') ?>
</div>
<?php yii\widgets\ActiveForm::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
,
CDbCriteria,
and
CDbCommandBuilder.
Yii 2.0 represents a DB query in terms
Query object that can be turned
QueryBuilder behind the scene. For
of a
CDbCommand
into a SQL statement with the help of
example:
10
CHAPTER 1.
INTRODUCTION
$query = new \yii\db\Query();
$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
yii\db\ActiveQuery in Yii 2.
yii\db\Query, and thus inherits all query building
You call yii\db\ActiveRecord::find() to start building a query:
CDbCriteria
class in 1.1 is replaced by
That class extends from
methods.
// To retrieve all *active* customers and order them by their ID:
$customers = Customer::find()
->where(['status' => $active])
->orderBy('id')
->all();
To declare a relation, simply dene a getter method that returns an
object.
ActiveQuery
The property name dened by the getter represents the relation
orders relation
place relations()):
name. For example, the following code declares an
you would have to declare relations in a central
(in 1.1,
class Customer extends \yii\db\ActiveRecord
{
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-y
relational query with a customized query condition:
$orders = $customer->getOrders()->andWhere('status=1')->all();
When eager loading a relation, Yii 2.0 does it dierently from 1.1. In particular, 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 rst statement brings back the primary records and the
second brings back the relational records by ltering with the primary keys
of the primary records.
1.2.
UPGRADING FROM VERSION 1.1
Instead of returning
11
ActiveRecord objects, you may chain the 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 signicantly 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 dene 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
\db\ActiveRecord::instantiate().
yii
There are many other changes and enhancements to Active Record.
Please refer to the Active Record section for more details.
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
yii\base\Behavior.
If the behavior class needs to respond to some events
of the owner, you have to override the
events()
method like the following,
namespace app\components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// ...
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
}
public function beforeValidate($event)
{
// ...
}
12
CHAPTER 1.
INTRODUCTION
1.2.22 User and IdentityInterface
The
CWebUser class in 1.1 is
CUserIdentity class.
no more
\IdentityInterface
now replaced by
yii\web\User,
and there is
Instead, you should implement the
yii\web
which is much more straightforward to use. The ad-
vanced project template provides such an example.
Please refer to the Authentication, Authorization, and Advanced Project
Template
13 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
and
post/1/popular.
post/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],
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.
13
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
Chapter 2
Getting Started
2.1 Installing Yii
1 package manager or by
You can install Yii in two ways, using the Composer
downloading an archive le. The former is the preferred way, as it allows you
to install new extensions or update Yii by simply running a single command.
Standard installations of Yii result in both the framework and a project
template being downloaded and installed. A project template is a working
Yii project implementing some basic features, such as login, contact form,
etc. Its code is organized in a recommended way. Therefore, it can serve as
a good starting point for your projects.
In this and the next few sections, we will describe how to install Yii with
the so-called
Basic Project Template
and how to implement new features on
top of this template. Yii also provides another template called the Advanced
Project Template
2 which is better used in a team development environment
to develop applications with multiple tiers.
Info: The Basic Project Template is suitable for developing 90
percent of Web applications. It diers from the Advanced Project
Template mainly in how their code is organized. If you are new
to Yii, we strongly recommend you stick to the Basic Project
Template for its simplicity yet sucient functionalities.
2.1.1 Installing via Composer
If you do not already have Composer installed, you may do so by following
3
the instructions at getcomposer.org . On Linux and Mac OS X, you'll run
the following commands:
1
http://getcomposer.org/
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
3
https://getcomposer.org/download/
2
13
14
CHAPTER 2.
GETTING STARTED
curl -s http://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
4
On Windows, you'll download and run Composer-Setup.exe .
5 if you encounter any prob-
Please refer to the Composer Documentation
lems or want to learn more about Composer usage.
If you had Composer already installed before, make sure you use an up
to date version. You can update Composer by running
composer self-update.
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"
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
The rst command installs the composer asset plugin
6 which allows man-
aging bower and npm package dependencies through Composer. You only
need to run this command once for all. The second command installs Yii in
a directory named
basic.
You can choose a dierent directory name if you
want.
Note: During the installation Composer may ask for your Github
login credentials. This is normal because Composer needs to get
enough API rate-limit to retrieve the dependent package information from Github. For more details, please refer to the Composer
7
documentation .
Tip:
If you want to install the latest development version of
Yii, you may use the following command instead, which adds a
8
stability option :
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 le involves three steps:
9
1. Download the archive le from yiiframework.com .
4
https://getcomposer.org/Composer-Setup.exe
https://getcomposer.org/doc/
6
https://github.com/francoispluchino/composer-asset-plugin/
7
https://getcomposer.org/doc/articles/troubleshooting.md#
api-rate-limit-and-oauth-tokens
8
https://getcomposer.org/doc/04-schema.md#minimum-stability
9
http://www.yiiframework.com/download/
5
2.1.
INSTALLING YII
15
2. Unpack the downloaded le to a Web-accessible folder.
3. Modify the
config/web.php le by entering a secret key for the cookieValidationKey
conguration 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 most projects, either small or big. It is especially suitable
if 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
10 .
Advanced Project Template
2.1.4 Verifying the Installation
After installation, you can use your browser to access the installed Yii application 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.
10
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
16
CHAPTER 2.
GETTING STARTED
You should see the above Congratulations! page in your browser.
If
not, please check if your PHP installation satises 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 congure your PHP installation so that it meets the minimum requirements of Yii. Most importantly, you should have PHP 5.4 or above. You
11 and a corresponding database
should also install the PDO PHP Extension
driver (such as
pdo_mysql
for MySQL databases), if your application needs a
database.
2.1.5 Conguring 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 server
11
12 or an Nginx HTTP server13 ,
http://www.php.net/manual/en/pdo.installation.php
http://httpd.apache.org/
13
http://nginx.org/
12
2.1.
INSTALLING YII
17
on Windows, Mac OS X, or Linux running PHP 5.4 or higher. Yii 2.0 is also
14 . However, there are some edge cases
compatible with facebook's HHVM
where HHVM behaves dierent than native PHP, so you have to take some
extra care when using HHVM.
On a production server, you may want to congure your Web server
http://www.example.com
http://www.example.com/basic/web/index.php. Such con-
so that the application can be accessed via the URL
/index.php
instead of
guration 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 de-
scribed in the Routing and URL Creation section. In this subsection, you'll
learn how to congure your Apache or Nginx server to achieve these goals.
Info: By setting
basic/web
as the document root, you also pre-
vent end users from accessing your private application code and
sensitive data les 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
conguration, you may still adjust the structure of your application for better security.
Please refer to the Shared Hosting
Environment section for more details.
Recommended Apache Conguration
Use the following conguration in Apache's
httpd.conf le or within
path/to/basic/web
host conguration. Note that you should replace
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
# 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>
14
http://hhvm.com/
a virtual
with the
18
CHAPTER 2.
GETTING STARTED
Recommended Nginx Conguration
To use Nginx
15 , you should install PHP as an FPM SAPI16 . You may use
the following Nginx conguration, replacing
path for
basic/web
and
mysite.local
path/to/basic/web with the actual
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;
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_params;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
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;
}
}
When using this conguration, you should also set
php.ini
cgi.fix_pathinfo=0
stat() calls.
Also note that when running an HTTPS server, you need to add
HTTPS on;
15
16
in the
le in order to avoid many unnecessary system
fastcgi_param
so that Yii can properly detect if a connection is secure.
http://wiki.nginx.org/
http://php.net/install.fpm
2.2.
RUNNING APPLICATIONS
19
2.2 Running Applications
After installing Yii, you have a working Yii application that can be accessed via the URL
index.php,
http://hostname/basic/web/index.php
depending upon your conguration.
or
http://hostname/
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 congured 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 accordingly.
Note that unlike framework itself, after project template is installed it's all
yours. You're free to add or delete code and overall modify it as you need.
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 nd 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 dierent 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.
Additionally to the web application, there is a console script called
yii,
which is located in the applications base directory. This script can be used
to run background and maintainance tasks for the application, which are
described in the Console Application Section.
2.2.2 Application Structure
The most important directories and les in your application are (assuming
the application's root directory is
basic):
20
CHAPTER 2.
GETTING STARTED
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 les 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)
is reected in the above directory organization.
tains all model classes, the
controllers
views
17 design pattern, which
The
models
directory con-
directory contains all view scripts, and the
directory contains all controller classes.
The following diagram shows the static structure of an application.
17
http://wikipedia.org/wiki/Model-view-controller
2.2.
RUNNING APPLICATIONS
Each application has an entry script
21
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.
22
CHAPTER 2.
1. A user makes a request to the entry script
GETTING STARTED
web/index.php.
2. The entry script loads the application conguration and creates an
application instance to handle the request.
3. The application resolves the requested route with the help of the request application component.
4. The application creates a controller instance to handle the request.
5. The controller creates an action instance and performs the lters for
the action.
6. If any lter fails, the action is cancelled.
7. If all lters 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.
2.3.
SAYING HELLO
23
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
rameter from the request and displays that message back to the user.
the request does not provide a
message
paIf
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
SiteController. This
controllers/SiteController.php. Here is
action in the existing
class le
controller is dened in the
the start of the new action:
<?php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
// ...existing code...
}
public function actionSay($message = 'Hello')
{
return $this->render('say', ['message' => $message]);
}
In the above code, the
in the
SiteController
say
action is dened as a method named
class. Yii uses the prex
action
actionSay
to dierentiate action
methods from non-action methods in a controller class. The name after the
action
prex maps to the action's ID.
24
CHAPTER 2.
GETTING STARTED
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.,
-comment).
create
Action method names are mapped to action IDs by removing any
dashes from the IDs, capitalizing the rst letter in each word, and prexing
action.
the resulting string with
The action method in our example
value defaults to
"Hello"
create-comment
actionCreateComment.
takes a parameter $message, whose
For example, the action ID
corresponds to the action method name
(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
"Goodbye",
parameter with a value of
the
$message
variable within the
action will be assigned that value.
Within the action method,
say.
The
message
render() is called to render a view le named
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:
<?php
use yii\helpers\Html;
?>
<?= Html::encode($message) ?>
The
say view should be saved in the le views/site/say.php.
render()
When the method
is called in an action, it will look for a PHP le named as
ControllerID/ViewName.php.
Note that in the above code, the
before being printed.
message
parameter is
views/
HTML-encoded
This is necessary as the parameter comes from an
end user, making it vulnerable to cross-site scripting (XSS) attacks
18 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
view is just a PHP script that is executed by the
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.
18
http://en.wikipedia.org/wiki/Cross-site_scripting
2.3.
SAYING HELLO
25
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
actionSay()
message
is passed as a parameter to the
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
render()
method will automatically embed
say view in a so-called
views/layouts/main.php.
the result of the
is located at
The
r
layout which in this case
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
controller class should be instantiated to handle the request.
controller will use the
ActionID
Then, the
part to determine which action should be
instantiated to do the real work.
In this example case, the route
SiteController controller class and the say
SiteController::actionSay() method will be called to
site/say
will be resolved to the
action. As
a result, the
handle the
request.
26
CHAPTER 2.
GETTING STARTED
Info: Like actions, controllers also have IDs that uniquely identify
them in an application.
rules as action IDs.
Controller IDs use the same naming
Controller class names are derived from
controller IDs by removing dashes from the IDs, capitalizing the
rst letter in each word, and suxing the resulting string with
the word
Controller.
post-comment
PostCommentController.
For example, the controller ID
corresponds to the controller class name
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
specic request.
content.
And you also created a view to compose the response's
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 eld and an
email input eld.
After getting those two pieces of information from the
user, the page will echo the entered values back for conrmation.
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
2.4.1 Creating a Model
EntryForm
models/EntryForm.php. Please
The data to be requested from the user will be represented by an
model class as shown below and saved in the le
refer to the Class Autoloading section for more details about the class le
naming convention.
<?php
namespace app\models;
use yii\base\Model;
2.4.
WORKING WITH FORMS
27
class EntryForm extends Model
{
public $name;
public $email;
public function rules()
{
return [
[['name', 'email'], 'required'],
['email', 'email'],
];
}
}
The class extends from
yii\base\Model,
a base class provided by Yii, com-
monly used to represent form data.
Info:
yii\base\Model
is used as a parent for model classes
associated with database tables.
yii\db\ActiveRecord
not
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
the
email
If you have an
name
and
email
values are required
data must be a syntactically valid email address
EntryForm
you may call its
object populated with the data entered by a user,
validate()
validation failure will set the
to trigger the data validation routines. A data
hasErrors property to true, and you may learn
errors.
what validation errors occurred through
<?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.
28
CHAPTER 2.
GETTING STARTED
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\models\EntryForm;
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 rst creates an
model with the data from
post().
EntryForm object. It then tries to populate the
$_POST, provided in Yii by yii\web\Request::
If the model is successfully populated (i.e., if the user has submitted
the HTML form), the action will call
validate()
to make 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 loca-
request, response, db, etc.
request
component of the application instance is used to access the $_POST
tor that provides components such as
to support specic functionality. In the above code, the
data.
If everything is ne, the action will render a view named
entry-confirm
to
conrm the successful submission of the data to the user. If no data is submitted 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 conrmation page upon valid data submission.
In practice, you should
2.4.
WORKING WITH FORMS
consider using
refresh()
19 .
or
29
redirect()
to avoid form resub-
mission problems
2.4.3 Creating Views
Finally, create two view les named
entry
entry-confirm
rendered by the
The
entry-confirm
and
entry.
These will be
action, as just described.
view simply displays the name and email data.
should be stored in the le
It
views/site/entry-confirm.php.
<?php
use yii\helpers\Html;
?>
<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>
entry view displays
views/site/entry.php.
The
an HTML form.
It should be stored in the le
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'name') ?>
<?= $form->field($model, 'email') ?>
<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
The view uses a powerful widget called
form. The
begin()
and
end()
ActiveForm
to build the HTML
methods of the widget render the opening and
closing form tags, respectively. Between the two method calls, input elds are
created by the
field() method.
The rst input eld is for the name data,
and the second for the email data. After the input elds, the
\Html::submitButton()
yii\helpers
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
19
http://en.wikipedia.org/wiki/Post/Redirect/Get
30
CHAPTER 2.
GETTING STARTED
You will see a page displaying a form with two input elds.
In front of
each input eld, 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 eld.
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.
2.4.
WORKING WITH FORMS
31
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 eld 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 JavaScript,
and secondarily performed on the server side via PHP.
yii\widgets\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 JavaScript 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 required, whether or not client-side validation is in place.
The labels for input elds are generated by the
field() method, using the
Name will be generated
property names from the model. For example, the label
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') ?>
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.
32
CHAPTER 2.
GETTING STARTED
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 congure a database connection, create an Active Record class, dene
an action, and create a view.
Through this tutorial, you will learn how to:
•
•
•
•
Congure a DB connection
Dene 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 nish 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.
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
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INSERT
INTO
INTO
INTO
INTO
INTO
INTO
INTO
INTO
INTO
INTO
`country`
`country`
`country`
`country`
`country`
`country`
`country`
`country`
`country`
`country`
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
VALUES
('AU','Australia',18886000);
('BR','Brazil',170115000);
('CA','Canada',1147000);
('CN','China',1277558000);
('DE','Germany',82164700);
('FR','France',59225700);
('GB','United Kingdom',59623400);
('IN','India',1013662000);
('RU','Russia',146934000);
('US','United States',278357000);
At this point, you have a database named
yii2basic,
and within it a
table with three columns, containing ten rows of data.
country
2.5.
WORKING WITH DATABASES
33
2.5.2 Conguring a DB Connection
20 PHP ex-
Before proceeding, make sure you have installed both the PDO
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 le
config/db.php
and change the parame-
ters to be correct for your database. By default, the le contains the following:
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
The
config/db.php
le is a typical le-based conguration tool. This particu-
lar conguration le species the parameters needed to create and initialize
a
yii\db\Connection
instance through which you can make SQL queries
against the underlying database.
The DB connection congured above can be accessed in the application
code via the expression
Info: The
Yii::$app->db.
config/db.php le will be included by the main appliconfig/web.php, which species how the ap-
cation conguration
plication instance should be initialized.
For more information,
please refer to the Congurations section.
2.5.3 Creating an Active Record
To represent and fetch the data in the
derived class named
Country,
country table,
and save it in the le
create an Active Record-
models/Country.php.
<?php
namespace app\models;
use yii\db\ActiveRecord;
class Country extends ActiveRecord
{
}
The
Country
class extends from
write any code inside of it!
yii\db\ActiveRecord.
With just the above code, Yii will guess the
associated table name from the class name.
20
You do not need to
http://www.php.net/manual/en/book.pdo.php
34
CHAPTER 2.
Info:
GETTING STARTED
If no direct match can be made from the class name to
the table name, you can override the
tableName()
yii\db\ActiveRecord::
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 app\models\Country;
// 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 nd 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.
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 specically 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 app\controllers;
use yii\web\Controller;
use yii\data\Pagination;
use app\models\Country;
class CountryController extends Controller
{
public function actionIndex()
{
$query = Country::find();
2.5.
WORKING WITH DATABASES
35
$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,
]);
}
controllers/CountryController.php.
Country::find(). This Active Record method builds
retrieves all of the data from the country table. To limit the
Save the above code in the le
The
index
action calls
a DB query and
number of countries returned in each request, the query is paginated with
the help of a
yii\data\Pagination 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.
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
country. This
folder will be used to hold all the views rendered by the country controller.
Within the views/country directory, create a le named index.php containing
Under the
views
directory, rst create a sub-directory named
the following:
<?php
use yii\helpers\Html;
use yii\widgets\LinkPager;
?>
<h1>Countries</h1>
<ul>
<?php foreach ($countries as $country): ?>
<li>
<?= Html::encode("{$country->name} ({$country->code})") ?>:
<?= $country->population ?>
</li>
36
CHAPTER 2.
GETTING STARTED
<?php endforeach; ?>
</ul>
<?= LinkPager::widget(['pagination' => $pagination]) ?>
The view has two sections relative to displaying the country data.
In the
rst part, the provided country data is traversed and rendered as an unordered HTML list. In the second part, a
yii\widgets\LinkPager
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
At rst, you will see a page showing ve 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 ve countries in the database: the second
page of records. Observe more carefully and you will nd that the URL in
the browser also changes to
http://hostname/index.php?r=country/index&page=2
Behind the scenes,
Pagination is providing all of the necessary functionality
to paginate a data set:
2.6.
GENERATING CODE WITH GII
•
Initially,
37
Pagination represents the rst page, which reects the coun-
try SELECT query with the clause
LIMIT 5 OFFSET 0.
As a result, the
rst ve countries will be fetched and displayed.
•
The
LinkPager widget renders the page buttons using the URLs crePagination. The URLs will contain the query parameter page,
ated by
which represents the dierent page numbers.
•
country/
page query
If you click the page button 2, a new request for the route
index
will be triggered and handled.
Pagination
reads the
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 ve countries for display.
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
and
yii\widgets\LinkPager.
yii\data\Pagination
In the next section, you will learn how to use the powerful code generation tool, called Gii, to help you rapidly implement some commonly required 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 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
21 is provided in Yii as a module. You can enable Gii by conguring it in
the
modules
property of the application. Depending upon how you created
your application, you may nd the following code is already provided in the
config/web.php
21
conguration le:
https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md
38
CHAPTER 2.
GETTING STARTED
$config = [ ... ];
if (YII_ENV_DEV) {
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = 'yii\gii\Module';
}
The above conguration states that when in development environment, the
application should include a module named
\Module.
If you check the entry script
gii,
web/index.php
nd the following line, which essentially makes
which is of class
yii\gii
of your application, you will
YII_ENV_DEV
to be true.
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 conguration. 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 localhost, the access will be denied by default for security purpose.
You can congure Gii to add the allowed IP addresses as follows,
'gii' => [
'class' => 'yii\gii\Module',
'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '
192.168.178.20'] // adjust this to your needs
],
2.6.
GENERATING CODE WITH GII
39
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 ll out the form as follows:
•
•
Table Name:
Model Class:
country
Country
Next, click on the Preview button. You will see
models/Country.php
is
listed in the resulting class le to be created. You may click on the name of
the class le to preview its content.
When using Gii, if you have already created the same le and would be
overwriting it, click the
diff button next to the le name to see the dierences
between the code to be generated and the existing version.
40
CHAPTER 2.
GETTING STARTED
When overwriting an existing le, check the box next to overwrite and
then click the Generate button. If creating a new le, you can just click
Generate.
Next, you will see a conrmation page indicating the code has been
successfully generated. If you had an existing le, 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 functionality using Gii, select the CRUD Generator (by clicking the link on the
Gii index page).
For the country example, ll out the resulting form as
follows:
•
•
•
app\models\Country
app\models\CountrySearch
Class: app\controllers\CountryController
Model Class:
Search Model Class:
Controller
2.6.
GENERATING CODE WITH GII
Next, click on the Preview button.
generated, as shown below.
41
You will see a list of les to be
42
CHAPTER 2.
If you previously created the
/country/index.php
GETTING STARTED
controllers/CountryController.php
and
views
les (in the databases section of the guide), check the
overwrite box to replace them.
(The previous versions did not have full
CRUD support.)
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 lter it by entering lter 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.6.
GENERATING CODE WITH GII
43
The following is the list of the les generated by Gii, in case you want to
investigate how these features are implemented, or to customize them:
•
•
controllers/CountryController.php
models/Country.php and models/CountrySearch.php
Controller:
Models:
44
CHAPTER 2.
•
Views:
GETTING STARTED
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 chapter, 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
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 lling out some forms.
This section will summarize the Yii resources available to help you be
more productive when using the framework.
•
Documentation
22 : As the name indicates, the guide precisely
The Denitive Guide
denes 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.
23 : This species the usage of every class
The Class Reference
provided by Yii.
It should be mainly used when you are writ-
ing code and want to understand the usage of a particular class,
method, property. Usage of the class reference is best only after
a contextual understanding of the entire framework.
24 : The wiki articles are written by Yii users
The Wiki Articles
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 Denitive Guide, they are useful in that they cover broader
topics and can often provide ready-to-use solutions.
22
http://www.yiiframework.com/doc-2.0/guide-README.html
http://www.yiiframework.com/doc-2.0/index.html
24
http://www.yiiframework.com/wiki/?tag=yii2
23
2.7.
LOOKING AHEAD
•
Books
45
25
26
Extensions : 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
25
26
Forum:
http://www.yiiframework.com/forum/
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
Stackoverow: http://stackoverflow.com/questions/tagged/
yii2
IRC chat:
http://www.yiiframework.com/doc/
http://www.yiiframework.com/extensions/
46
CHAPTER 2.
GETTING STARTED
Chapter 3
Application Structure
3.1 Overview
1
Yii applications are organized according to the model-view-controller (MVC)
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 application components and coordinate them to fulll requests.
•
application components: they are objects registered with applications
and provide various services for fullling requests.
•
modules: they are self-contained packages that contain complete MVC
by themselves. An application can be organized in terms of multiple
modules.
•
lters: 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 dierent 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 rst 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 sux).
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:
•
•
•
•
•
2
Dene global constants;
2
Register Composer autoloader ;
Include the
Yii
class le;
Load application conguration;
Create and congure an application instance;
http://getcomposer.org/doc/01-basic-usage.md#autoloading
3.2.
•
ENTRY SCRIPTS
Call
yii\base\Application::run()
49
to process the incoming request.
3.2.1 Web Applications
The following is the code in the entry script for the Basic Web Project
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 yii\web\Application($config))->run();
3.2.2 Console Applications
Similarly, the following is the code for the entry script of a console application:
#!/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
require(__DIR__ . '/vendor/yiisoft/yii2/Yii.php');
// load application configuration
$config = require(__DIR__ . '/config/console.php');
50
CHAPTER 3.
APPLICATION STRUCTURE
$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);
3.2.3 Dening Constants
Entry scripts are the best place for dening global constants. Yii supports
the following three constants:
• YII_DEBUG:
species 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_ENV:
YII_DEBUG
is false.
species which environment the application is running in. This
will be described in more detail in the Congurations section.
default value of
YII_ENV
is
'prod',
The
meaning the application is running
in production environment.
• YII_ENABLE_ERROR_HANDLER:
species whether to enable the error handler
provided by Yii. The default value of this constant is true.
When dening 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 denitions should be done at the very beginning of an entry
script so that they can take eect when other PHP les 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 appli-
cation object which is created in the entry script and is globally accessible
through the expression
\Yii::$app.
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:
Web applications and console applications.
As the names indicate, the former mainly handles Web requests while the
latter console command requests.
3.3.
APPLICATIONS
51
3.3.1 Application Congurations
When an entry script creates an application, it will load a conguration 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 yii\web\Application($config))->run();
Like normal congurations, application congurations specify how to initialize properties of application objects. Because application congurations
are often very complex, they usually are kept in conguration les, like the
web.php
le in the above example.
3.3.2 Application Properties
There are many important application properties that you should congure
in application congurations. These properties typically describe the environment that applications are running in.
For example, applications need
to know how to load controllers, where to store temporary les, etc. In the
following, we will summarize these properties.
Required Properties
In any application, you should at least congure two properties:
basePath.
id
The
id
and
id property species a unique ID that dierentiates an application
from others. It is mainly used programmatically. Although not a requirement, for best interoperability it is recommended that you use alphanumeric
characters only when specifying an application ID.
basePath
The
basePath
property species the root directory of an appli-
cation. It is the directory that contains all protected source code of an application system. Under this directory, you normally will see sub-directories
such as
models, views, controllers,
which contain source code corresponding
to the MVC pattern.
You may congure the
path alias.
basePath
property using a directory path or a
In both forms, the corresponding directory must exist, or an
exception will be thrown. The path will be normalized by calling the
()
function.
realpath
52
CHAPTER 3.
The
APPLICATION STRUCTURE
basePath property is often used to derive other important paths (e.g.
the runtime path). For this reason, a path alias named
@app
is predened 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 congured because they dier across dierent applications.
aliases
This property allows you to dene a set of aliases in terms of
an array.
The array keys are alias names, and the array values are the
corresponding path denitions. For example,
[
]
'aliases' => [
'@name1' => 'path/to/path1',
'@name2' => 'path/to/path2',
],
This property is provided such that you can dene aliases in terms of application congurations instead of the method calls
bootstrap
Yii::setAlias().
This is a very useful property. It allows you to specify an array
of components that should be run during the application
process.
bootstrapping
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 specied in one of the
following formats:
•
•
•
•
•
an application component ID as specied via components.
a module ID as specied via modules.
a class name.
a conguration array.
an anonymous function that creates and returns a component.
For example,
[
'bootstrap' => [
// an application component ID or module ID
'demo',
// a class name
'app\components\Profiler',
// a configuration array
[
'class' => 'app\components\Profiler',
'level' => 3,
3.3.
APPLICATIONS
53
],
]
],
// an anonymous function
function () {
return new app\components\Profiler();
}
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
yii\base\BootstrapInterface, its bootstrap()
method will also be called.
Another practical example is in the application conguration for the Basic Project Template, where the
debug
and
gii
modules are congured 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'] = 'yii\debug\Module';
}
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = 'yii\gii\Module';
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.
catchAll
This property is supported by
Web applications only.
It spec-
ies 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 conguration is an array whose rst element species 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,
54
[
]
CHAPTER 3.
APPLICATION STRUCTURE
'catchAll' => [
'offline/notice',
'param1' => 'value1',
'param2' => 'value2',
],
components
This is the single most important property. It allows you to
register a list of named components called application components that you
can use in other places. For example,
[
]
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
],
Each application component is specied as a key-value pair in the array. The
key represents the component ID, while the value represents the component
class name or conguration.
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.
controllerMap
This property allows you to map a controller ID to an
arbitrary controller class. By default, Yii maps controller IDs to controller
classes based on a convention (e.g.
controllers\PostController).
the ID
post
would be mapped to
convention for specic controllers. In the following example,
app\controllers\UserController,
app\controllers\PostController.
mapped to
[
]
app\
By conguring this property, you can break the
while
article
account
will be
will be mapped to
'controllerMap' => [
[
'account' => 'app\controllers\UserController',
'article' => [
'class' => 'app\controllers\PostController',
'enableCsrfValidation' => false,
],
],
],
3.3.
APPLICATIONS
55
The array keys of this property represent the controller IDs, while the array
values represent the corresponding controller class names or congurations.
controllerNamespace
This property species the default namespace un-
der which controller classes should be located. It defaults to
If a controller ID is
post,
PostController,
app\controllers\PostController.
name (without namespace) would be
class name would be
app\controllers.
by convention the corresponding controller class
and the fully qualied
Controller classes may also be located under sub-directories of the directory corresponding to this namespace. For example, given a controller ID
admin/post, the corresponding fully
controllers\admin\PostController.
qualied controller class would be
app\
It is important that the fully qualied controller classes should be autoloadable 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
congure the controllerMap property.
language
This property species the language in which the application
should display content to end users.
en,
The default value of this property is
meaning English. You should congure this property if your application
needs to support multiple languages.
The value of this property determines various internationalization aspects, including message translation, date formatting, number formatting,
etc.
For example, the
yii\jui\DatePicker
widget will use this property
value by default to determine in which language the calendar should be displayed and how should the date be formatted.
It is recommended that you specify a language in terms of an IETF
3
language tag .
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.
modules
This property species the modules that the application contains.
The property takes an array of module classes or congurations with the
array keys being the module IDs. For example,
[
'modules' => [
// a "booking" module specified with the module class
'booking' => 'app\modules\booking\BookingModule',
3
http://en.wikipedia.org/wiki/IETF_language_tag
56
CHAPTER 3.
],
]
APPLICATION STRUCTURE
// a "comment" module specified with a configuration array
'comment' => [
'class' => 'app\modules\comment\CommentModule',
'db' => 'db',
],
Please refer to the Modules section for more details.
name
This property species the application name that may be displayed
to end users. Unlike the
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 congure this property if none of your code
is using it.
params
This property species an array of globally accessible application
parameters. Instead of using hardcoded numbers and strings everywhere in
your code, it is a good practice to dene them as application parameters in
a single place and use the parameters in places where needed. For example,
you may dene 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];
Later if you decide to change the thumbnail size, you only need to modify it
in the application conguration without touching any dependent code.
sourceLanguage
code is written in.
This property species the language that the application
The default value is
'en-US',
meaning English (United
States). You should congure this property if the text content in your code
is not in English.
Like the language property, you should congure this property in terms
4
of an IETF language tag . 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.
4
http://en.wikipedia.org/wiki/IETF_language_tag
3.3.
APPLICATIONS
timeZone
57
This property is provided as an alternative way of setting the
default time zone of PHP runtime.
By conguring this property, you are
5
essentially calling the PHP function date_default_timezone_set() .
For
example,
[
'timeZone' => 'America/Los_Angeles',
]
version
This property species the version of the application. It defaults
'1.0'.
You do not always need to congure this property if none of your
to
code is using it.
Useful Properties
The properties described in this subsection are not commonly congured
because their default values stipulate common conventions.
However, you
may still congure them in case you want to break the conventions.
charset
This property species 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.
defaultRoute
This property species 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,
admin/post/create.
help, post/create,
If action ID is not given, it will take the default value as
yii\base\Controller::$defaultAction.
Web applications, the default value of this property is 'site', which
specied in
For
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
app\controllers\SiteController::actionIndex().
console applications, the default value is 'help', which means the
core command yii\console\controllers\HelpController::actionIndex()
For
yii
should be used. As a result, if you run the command
without providing
any arguments, it will display the help information.
extensions
This property species the list of extensions that are installed
and used by the application. By default, it will take the array returned by
the le
@vendor/yiisoft/extensions.php.
The
extensions.php
le is generated
6 to install extensions.
and maintained automatically when you use Composer
So in most cases, you do not need to congure this property.
5
6
http://php.net/manual/en/function.date-default-timezone-set.php
http://getcomposer.org
58
CHAPTER 3.
APPLICATION STRUCTURE
In the special case when you want to maintain extensions manually, you
may congure this property like the following:
[
'extensions' => [
[
'name' => 'extension name',
'version' => 'version number',
'bootstrap' => 'BootstrapClassName',
configuration array
'alias' => [ // optional
'@alias1' => 'to/path1',
'@alias2' => 'to/path2',
],
],
// optional, may also be a
// ... more extensions like the above ...
]
],
As you can see, the property takes an array of extension specications. Each
name and version elements. If
bootstrap process, a bootstrap element
extension is specied with an array consisting of
an extension needs to run during the
may be specied with a bootstrapping class name or a conguration array.
An extension may also dene a few aliases.
layout
This property species the name of the default layout that should
be used when rendering a view.
layout le
main.php
The default value is
'main',
under the layout path should be used.
meaning the
If both of the
layout path and the view path are taking the default values, the default
layout le can be represented as the path alias
You may congure this property to be
false
@app/views/layouts/main.php.
if you want to disable layout
by default, although this is very rare.
layoutPath
looked for.
path.
This property species the path where layout les should be
The default value is the
layouts
sub-directory under the view
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 congure it as a directory or a path alias.
runtimePath
This property species the path where temporary les, such
as log les, cache les, can be generated. The default value is the directory
represented by the alias
@app/runtime.
You may congure 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
les under it may contain sensitive information.
3.3.
APPLICATIONS
59
To simplify accessing to this path, Yii has predened a path alias named
@runtime
for it.
viewPath
located.
views.
This property species the root directory where view les are
The default value is the directory represented by the alias
@app/
You may congure it as a directory or a path alias.
vendorPath
This property species the vendor directory managed by Com-
7
poser . It contains all third party libraries used by your application, includ-
ing the Yii framework. The default value is the directory represented by the
alias
@app/vendor.
You may congure this property as a directory or a path alias. When you
modify this property, make sure you also adjust the Composer conguration
accordingly.
To simplify accessing to this path, Yii has predened a path alias named
@vendor
for it.
enableCoreCommands
only.
This property is supported by
console applications
It species whether the core commands included in the Yii release
should be enabled. The default value is
true.
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 congurations like the following,
[
'on beforeRequest' => function ($event) {
// ...
},
]
The use of the
on eventName
syntax is described in the Congurations section.
Alternatively, you may attach event handlers during the bootstrapping
process after the application instance is created. For example,
\Yii::$app->on(\yii\base\Application::EVENT_BEFORE_REQUEST, function ($event
) {
// ...
});
7
http://getcomposer.org
60
CHAPTER 3.
APPLICATION STRUCTURE
EVENT_BEFORE_REQUEST
This event is triggered
event name is
before
an application handles a request. The actual
beforeRequest.
When this event is triggered, the application instance has been congured
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
$language
yii\base\Application::
property based on some parameters.
EVENT_AFTER_REQUEST
This event is triggered
before
after
an application nishes handling a request but
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
response
component also triggers some events while it is
sending out response content to end users. Those events are triggered
after
this event.
EVENT_BEFORE_ACTION
This event is triggered
event name is
before
running every controller action.
The actual
beforeAction.
yii\base\ActionEvent. An event
yii\base\ActionEvent::$isValid property to be false
The event parameter is an instance of
handler may set the
to stop running the action. For example,
[
]
'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 rst ones triggering this event, followed
by modules (if any), and nally controllers. If an event handler sets
\ActionEvent::$isValid
to be
false,
yii\base
all the following events will NOT be
triggered.
EVENT_AFTER_ACTION
This event is triggered
event name is
after
afterAction.
running every controller action.
The actual
3.3.
APPLICATIONS
61
The event parameter is an instance of
the
yii\base\ActionEvent::$result
yii\base\ActionEvent.
Through
property, an event handler may ac-
cess 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 rst objects triggering this event,
followed by modules (if any), and nally 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 conguration as an array.
2. The entry script creates a new instance of the application:
62
CHAPTER 3.
• preInit()
APPLICATION STRUCTURE
is called, which congures some high priority applica-
basePath.
• Register the error handler.
• Congure application properties.
• init() is called which further calls bootstrap()
tion properties, such as
to run boot-
strapping components.
3. The entry script calls
yii\base\Application::run()
to run the ap-
plication:
•
•
Trigger the
EVENT_BEFORE_REQUEST
event.
Handle the request: resolve the request into a route and the associated parameters; create the module, controller and action objects
as specied by the route; and run the action.
•
•
Trigger the
EVENT_AFTER_REQUEST
event.
Send response to the end user.
4. The entry script receives the exit status from the application and completes the request processing.
3.4 Application Components
Applications are service locators. They host a set of the so-called
tion components
example, the
applica-
that provide dierent services for processing requests. For
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 identies 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
::$app->cache
to get the
\Yii::$app->db
primary cache
to get the
DB connection, and \Yii
registered with the application.
An application component is created the rst 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
conguring the
yii\base\Application::$components
tion congurations. For example,
[
'components' => [
// register "cache" component using a class name
'cache' => 'yii\caching\ApcCache',
property in applica-
3.4.
APPLICATION COMPONENTS
63
// register "db" component using a configuration array
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
],
]
],
// register "search" component using an anonymous function
'search' => function () {
return new app\components\SolrService;
},
Info:
While you can register as many application components
as you want, you should do this judiciously.
ponents are like global variables.
Application com-
Using too many application
components can potentially make your code harder to test and
maintain. In many cases, you can simply create a local component 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 rst 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
explicitly accessed. To do so, you may list its ID in the
bootstrap
property
of the application.
For example, the following application conguration makes sure the
log
component is always loaded:
[
]
'bootstrap' => [
'log',
],
'components' => [
'log' => [
// configuration for "log" component
],
],
3.4.2 Core Application Components
Yii denes a set of
core
application components with xed IDs and default
congurations. For example, the
request
component is used to collect in-
formation about a user request and resolve it into a route; the
db component
64
CHAPTER 3.
APPLICATION STRUCTURE
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 predened core application components. You may
congure and customize them like you do with normal application components.
When you are conguring a core application component, if you do
not specify its class, the default one will be used.
• assetManager:
manages asset bundles and asset publishing.
Please
refer to the Managing Assets section for more details.
• db:
represents a database connection through which you can perform
DB queries. Note that when you congure this component, you must
specify the component class as well as other required component properties, such as
yii\db\Connection::$dsn.
Please refer to the Data
Access Objects section for more details.
• errorHandler:
handles PHP errors and exceptions. Please refer to the
Handling Errors section for more details.
• formatter:
formats data when they are displayed to end users. For
example, a number may be displayed with thousand separator, a date
may be formatted in long format. Please refer to the Data Formatting
section for more details.
• i18n:
supports message translation and formatting. Please refer to the
Internationalization section for more details.
• log:
manages log targets. Please refer to the Logging section for more
details.
• mail:
supports mail composing and sending. Please refer to the Mail-
ing section for more details.
• response:
represents the response being sent to end users. Please refer
to the Responses section for more details.
• request:
represents the request received from end users. Please refer
to the Requests section for more details.
• session:
represents the session information. This component is only
available in
Web applications.
Please refer to the Sessions and Cook-
ies section for more details.
• urlManager:
supports URL parsing and creation. Please refer to the
URL Parsing and Generation section for more details.
• user:
represents the user authentication information. This component
is only available in
Web applications
Please refer to the Authentica-
tion section for more details.
• view:
supports view rendering. Please refer to the Views section for
more details.
3.5.
CONTROLLERS
65
3.5 Controllers
Controllers are part of the MVC
extending from
8 architecture. They are objects of classes
yii\base\Controller
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 nally generate outgoing
responses.
3.5.1 Actions
Controllers are composed of
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 app\controllers;
use
use
use
use
Yii;
app\models\Post;
yii\web\Controller;
yii\web\NotFoundHttpException;
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()
{
$model = new Post;
}
8
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
66
CHAPTER 3.
APPLICATION STRUCTURE
}
In the
view
action (dened by the
actionView()
method), the code rst loads
the model according to the requested model ID; If the model is loaded successfully, it will display it using a view named
view.
Otherwise, it will throw
an exception.
In the
create
action (dened by the
actionCreate()
method), the code is
similar. It rst tries to populate the model using the request data and save
view
action
with the ID of the newly created model. Otherwise it will display the
create
the model.
If both succeed it will redirect the browser to the
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 identies 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 identies 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
the
index
action in the
site
http://hostname/index.php?r=site/index,
controller will be executed. For more details on
how routes are resolved into actions, please refer to the Routing and URL
Generation section.
3.5.3 Creating Controllers
In
Web applications,
yii\web\Controller
console applications, controllers should
yii\console\Controller or its child classes. The following
controllers should extend from
or its child classes. Similarly in
extend from
code denes a
site
controller:
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
}
3.5.
CONTROLLERS
67
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.
article and post-comment
?, PostComment, admin\post are not.
example,
are both valid controller IDs, while
A controller ID may also contain a subdirectory prex.
admin/article
der the
stands for an
article
controller namespace.
controller in the
admin
For
article
For example,
subdirectory un-
Valid characters for subdirectory prexes
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 rst 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 sux
And prepend the
Controller.
controller namespace.
The followings are some examples, assuming the
takes the default value
•
•
•
•
controller namespace
app\controllers:
article derives app\controllers\ArticleController;
post-comment derives app\controllers\PostCommentController;
admin/post-comment derives app\controllers\admin\PostCommentController;
adminPanels/post-comment derives app\controllers\adminPanels\PostCommentController
.
Controller classes must be autoloadable. For this reason, in the above ex-
article controller class should be saved in the le whose alias
@app/controllers/ArticleController.php; while the admin/post2-comment controller should be in @app/controllers/admin/Post2CommentController.php.
amples, the
is
Info: The last example
admin/post2-comment
put a controller under a sub-directory of the
shows how you can
controller namespace.
This is useful when you want to organize your controllers into
several categories and you do not want to use modules.
68
CHAPTER 3.
APPLICATION STRUCTURE
Controller Map
You can congure
controller map
to overcome the constraints of the con-
troller IDs and class names described above. This is mainly useful when you
are using some third-party controllers which you do not have control over
their class names.
controller map in the application conguration like
You may congure
the following:
[
]
'controllerMap' => [
// declares "account" controller using a class name
'account' => 'app\controllers\UserController',
],
// declares "article" controller using a configuration array
'article' => [
'class' => 'app\controllers\PostController',
'enableCsrfValidation' => false,
],
Default Controller
Each application has a default controller specied via the
::$defaultRoute
route specied by this property will be used.
value is
yii\base\Application
property. When a request does not specify a route, the
For
Web applications,
'site', while for console applications, it
http://hostname/index.php, it means the site
a URL is
is
help.
its
Therefore, if
controller will handle
the request.
You may change the default controller with the following application
conguration:
[
]
'defaultRoute' => 'main',
3.5.4 Creating Actions
Creating actions can be as simple as dening the so-called
a controller class. An action method is a
with the word
action.
public
action methods
in
method whose name starts
The return value of an action method represents the
response data to be sent to end users. The following code denes two actions
index
and
hello-world:
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
3.5.
{
CONTROLLERS
69
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 letters in lower case, digits, underscores and dashes. The dashes in an actionID
are used to separate words. For example,
valid action IDs, while
view?, Update
view, update2, comment-post
are all
are not.
You can create actions in two ways: inline actions and standalone actions. An inline action is dened as a method in the controller class, while
a standalone action is a class extending
yii\base\Action
or its child class.
Inline actions take less eort 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 dierent controllers or be redistributed as
extensions.
Inline Actions
Inline actions refer to the actions that are dened 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 rst letter in each word of the action ID into upper case;
Remove dashes;
Prepend the prex
For example,
action.
index becomes actionIndex, and hello-world becomes actionHelloWorld
.
Note: The names of the action methods are
have a method named
ActionIndex,
case-sensitive.
If you
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
70
CHAPTER 3.
APPLICATION STRUCTURE
be public. A private or protected method does NOT dene an
inline action.
Inline actions are the most commonly dened actions because they take little
eort to create. However, if you plan to reuse the same action in dierent
places, or if you want to redistribute an action, you should consider dening
it as a
standalone action.
Standalone Actions
yii\base
there are yii
Standalone actions are dened in terms of action classes extending
\Action or its child classes. For example, in the Yii releases,
\web\ViewAction and yii\web\ErrorAction, both of which are standalone
actions.
To use a standalone action, you should declare it in the
overriding the
action map
by
yii\base\Controller::actions() method in your controller
classes like the following:
public function actions()
{
return [
// declares "error" action using a class name
'error' => 'yii\web\ErrorAction',
}
];
// declares "view" action using a configuration array
'view' => [
'class' => 'yii\web\ViewAction',
'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 congurations.
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
or its child class, and implement a public method named
the
run()
yii\base\Action
run().
The role of
method is similar to that of an action method. For example,
<?php
namespace app\components;
use yii\base\Action;
class HelloWorldAction extends Action
{
public function run()
{
return "Hello World";
}
3.5.
CONTROLLERS
71
}
Action Results
run()
The return value of an action method or the
method of a standalone
action is signicant. It stands for the result of the corresponding action.
The return value can be a response object which will be sent to the end
user as the response.
•
For
Web applications,
the return value can also be some arbitrary
data which will be assigned to
yii\web\Response::$data
and be fur-
ther converted into a string representing the response body.
•
For
console applications, the return value can also be
exit status of the command execution.
an integer
representing the
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
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
actions can take parameters, called
obtained from requests.
For
parameter is retrieved from
console applications,
Web applications,
$_GET
$id
and
methods for standalone
Their values are
the value of each action
using the parameter name as the key; for
they correspond to the command line arguments.
view
$version.
In the following example, the
two parameters:
run()
action parameters.
action (an inline action) has declared
namespace app\controllers;
use yii\web\Controller;
class PostController extends Controller
{
public function actionView($id, $version = null)
{
// ...
}
}
The action parameters will be populated as follows for dierent requests:
72
CHAPTER 3.
APPLICATION STRUCTURE
• http://hostname/index.php?r=post/view&id=123:
lled with the value
'123',
while
$version
the
$id
parameter will be
is still null because there is
version query parameter.
• http://hostname/index.php?r=post/view&id=123&version=2: the $id and $version
parameters will be lled with '123' and '2', respectively.
• http://hostname/index.php?r=post/view: a yii\web\BadRequestHttpException
exception will be thrown because the required $id parameter is not prono
vided in the request.
• http://hostname/index.php?r=post/view&id[]=123:
exception will be thrown because
pected array value
$id
a
yii\web\BadRequestHttpException
parameter is receiving an unex-
['123'].
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)
{
// ...
}
http://hostname/index.php?r=post/view&id[]=123, the $id
['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
Now if the request is
parameter will take the value of
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 specied via the
::$defaultAction
yii\base\Controller
property. When a route contains the controller ID only,
it implies that the default action of the specied 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 app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public $defaultAction = 'home';
public function actionHome()
{
return $this->render('home');
}
3.5.
CONTROLLERS
73
}
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 fulll the request:
1. The
yii\base\Controller::init()
method is called after the con-
troller is created and congured.
2. The controller creates an action object based on the requested action
ID:
•
If the action ID is not specied, the
default action ID
will be
used.
•
If the action ID is found in the
action map,
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
yii\base\InvalidRouteException
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.
74
CHAPTER 3.
APPLICATION STRUCTURE
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;
•
•
may use views to compose responses;
should NOT process the request data - this should be done in the model
layer;
•
should avoid embedding HTML or other presentational code - this is
better done in views.
3.6 Models
9 architecture. They are objects representing
Models are part of the MVC
business data, rules and logic.
You can create model classes by extending
classes. The base class
•
yii\base\Model
yii\base\Model
or its child
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
\base\Model.
yii
However, because there are many Yii components
built to support
yii\base\Model, it is usually the preferable base
class for a model.
9
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
3.6.
MODELS
75
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
attributes()
yii\base\Model::
species what attributes a model class has.
You can access an attribute like accessing a normal object property:
$model = new \app\models\ContactForm;
// "name" is an attribute of ContactForm
$model->name = 'example';
echo $model->name;
You can also access attributes like accessing array elements, thanks to the
10 and ArrayIterator11 by
support for ArrayAccess
yii\base\Model:
$model = new \app\models\ContactForm;
// accessing attributes like array elements
$model['name'] = 'example';
echo $model['name'];
// iterate attributes
foreach ($model as $name => $value) {
echo "$name: $value\n";
}
Dening Attributes
By default, if your model class extends directly from
its
non-static public
yii\base\Model,
member variables are attributes.
all
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 app\models;
use yii\base\Model;
class ContactForm extends Model
{
public $name;
public $email;
public $subject;
public $body;
}
You may override
yii\base\Model::attributes() to dene attributes in a
dierent way. The method should return the names of the attributes in a
model. For example,
10
11
yii\db\ActiveRecord does so by returning the column
http://php.net/manual/en/class.arrayaccess.php
http://php.net/manual/en/class.arrayiterator.php
76
CHAPTER 3.
APPLICATION STRUCTURE
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.
Attribute Labels
When displaying values or getting input for attributes, you often need to display 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
yii\base\Model::getAttributeLabel().
For example,
$model = new \app\models\ContactForm;
// displays "Name"
echo $model->getAttributeLabel('name');
By default, attribute labels are automatically generated from attribute names.
The generation is done by the method
yii\base\Model::generateAttributeLabel().
It will turn camel-case variable names into multiple words with the rst letter in each word in upper case. For example,
firstName
becomes
username
becomes
Username,
and
First Name.
If you do not want to use automatically generated labels, you may override
yii\base\Model::attributeLabels()
labels. For example,
namespace app\models;
use yii\base\Model;
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',
];
}
to explicitly declare attribute
3.6.
MODELS
77
For applications supporting multiple languages, you may want to translate
attribute labels.
This can be done in the
attributeLabels()
method as
well, like the following:
public function attributeLabels()
{
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 dene attribute labels. For example, based on
the scenario the model is being used in, you may return dierent 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 dierent
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 dierent scenarios, a model may use dierent
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
yii\base\Model::$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 validation rules declared in the model. However, you can customize this behavior
by overriding the
yii\base\Model::scenarios()
ing:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
method, like the follow-
78
CHAPTER 3.
{
APPLICATION STRUCTURE
public function scenarios()
{
return [
'login' => ['username', 'password'],
'register' => ['username', 'email', 'password'],
];
}
}
Info:
In the above and following examples, the model classes
are extending from
yii\db\ActiveRecord
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
register
The
password attributes are active in the login scenario; while in the
scenario, email is also active besides username and password.
default implementation of scenarios() will return all scenarios found
and
in the validation rule declaration method
overriding
scenarios(),
yii\base\Model::rules().
When
if you want to introduce new scenarios in addition to
the default ones, you may write code like the following:
namespace app\models;
use yii\db\ActiveRecord;
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 dierently 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 satises certain rules (called
as
business rules ).
to make sure all attributes are not
validation rules,
also known
ContactForm model, you may want
empty and the email attribute contains
For example, given a
3.6.
MODELS
79
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 x the errors.
You may call
yii\base\Model::validate()
to validate the received
data. The method will use the validation rules declared in
::rules()
to validate every relevant attribute. If no error is found, it will
return true.
$errors
yii\base\Model
Otherwise, it will keep the errors in the
yii\base\Model::
property and return false. For example,
$model = new \app\models\ContactForm;
// 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
\Model::rules()
yii\base
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'],
80
CHAPTER 3.
APPLICATION STRUCTURE
}
If you do not specify the
narios.
on
A rule is called an
scenario.
property, the rule would be applied in all sce-
active rule
if it can be applied in the current
An attribute will be validated if and only if it is an active attribute
declared in
declared in
scenarios()
rules().
and is associated with one or multiple active 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
yii\base\Model::$attributes
property. The following two pieces of code are equivalent, both trying to assign 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 \app\models\ContactForm;
$model->attributes = \Yii::$app->request->post('ContactForm');
$model = new \app\models\ContactForm;
$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
attributes listed in
safe attributes
which are the
yii\base\Model::scenarios() for the current scenario
of a model. For example, if the
User model has the following scenario declaralogin, only the username and password
tion, then when the current scenario is
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
modied by end user data. For example, if the
User
model has
3.6.
MODELS
a
81
permission
attribute which determines the permission assigned
to the user, you would like this attribute to be modiable by
administrators through a backend interface only.
yii\base\Model::scenarios() will
found in yii\base\Model::rules(), if
Because the default implementation of
return all scenarios and attributes
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
two purposes:
yii\base\Model::scenarios()
method serves for
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.
prexing an exclamation mark
scenarios(),
like the
secret
!
You can do so by
to the attribute name when declaring it in
attribute in the following:
public function scenarios()
{
return [
'login' => ['username', 'password', '!secret'],
];
}
login scenario, all three attributes will be validated.
username and password attributes can be massively assigned.
input value to the secret attribute, you have to do it explicitly
When the model is in the
However, only the
To assign an
as follows,
$model->secret = $secret;
3.6.5 Data Exporting
Models often need to be exported in dierent 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
rst step, models are converted into arrays; in the second step, the arrays are
converted into target formats. You may just focus on the rst step, because
82
CHAPTER 3.
APPLICATION STRUCTURE
the second step can be achieved by generic data formatters, such as
\JsonResponseFormatter.
yii\web
The simplest way of converting a model into an array is to use the
\base\Model::$attributes
yii
property. For example,
$post = \app\models\Post::findOne(100);
$array = $post->attributes;
By default, the
ues of
all
yii\base\Model::$attributes property will return the valyii\base\Model::attributes().
attributes declared in
A more exible and powerful way of converting a model into an array is
to use the
yii\base\Model::toArray() method.
yii\base\Model::$attributes.
same as that of
Its default behavior is the
However, it allows you to
choose which data items, called
elds,
how they should be formatted.
In fact, it is the default way of exporting
to be put in the resulting array and
models in RESTful Web service development, as described in the Response
Formatting.
Fields
A eld is simply a named element in the array that is obtained by calling
the
yii\base\Model::toArray()
method of a model.
By default, eld names are equivalent to attribute names. However, you
can change this behavior by overriding the
fields() and/or extraFields()
methods. Both methods should return a list of eld denitions. The elds
fields() are default elds, meaning that toArray() will return these
The extraFields() method denes additionally available
elds which can also be returned by toArray() as long as you specify them
via the $expand parameter. For example, the following code will return all
elds dened in fields() and the prettyName and fullAddress elds if they are
dened in extraFields().
dened by
elds by default.
$array = $model->toArray([], ['prettyName', 'fullAddress']);
fields() to add, remove, rename or redene elds.
fields() should be an array. The array keys are the
You can override
The
return value of
eld
names, and the array values are the corresponding eld denitions which
can be either property/attribute names or anonymous functions returning
the corresponding eld values. In the special case when a eld name is the
same as its dening 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
3.6.
MODELS
83
'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();
// 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
out.
fields()
to lter them
In the above example, we choose to lter out
password_hash
and
auth_key,
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 dierent places. In a well-designed application, 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 environmental 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.
84
CHAPTER 3.
APPLICATION STRUCTURE
You may usually consider the last recommendation above when you are developing 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 aect several dierent places. To make the model code more maintainable, you may take the
following strategy:
•
Dene a set of base model classes that are shared by dierent applications 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, dene a concrete
model class by extending from the corresponding base model class.
The concrete model classes should contain rules and logic that are
specic for that application or module.
12 , you may dene a base
For example, in the Advanced Project Template
model class
common\models\Post.
Then for the front end application, you de-
ne and use a concrete model class
frontend\models\Post
which extends from
common\models\Post. And similarly for the back end application, you dene
backend\models\Post. With this strategy, you will be sure that the code in
frontend\models\Post is only specic 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 MVC
13 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 les containing mainly
HTML code and presentational PHP code. They are managed by the
view
application component which provides commonly used methods to facilitate
view composition and rendering. For simplicity, we often call view templates
or view template les 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
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
13
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
3.7.
VIEWS
85
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/* @var $this yii\web\View */
/* @var $form yii\widgets\ActiveForm */
/* @var $model app\models\LoginForm */
$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
view component
managing and rendering this view template.
Besides
as
$model
pushed
$this,
there may be other predened variables in a view, such
in the above example. These variables represent the data that are
into the view by controllers or other objects which trigger the view
rendering.
Tip: The predened 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 lter the data coming from end users before presenting them.
Otherwise, your application may be subject to cross-site scripting
To display a plain text, encode it rst by calling
encode().
yii\helpers\Html::
For example, the following code encodes the user name before
displaying it:
<?php
use yii\helpers\Html;
?>
<div class="username">
<?= Html::encode($user->name) ?>
</div>
14
14 attacks.
http://en.wikipedia.org/wiki/Cross-site_scripting
86
CHAPTER 3.
To display HTML content, use
tent rst.
APPLICATION STRUCTURE
yii\helpers\HtmlPurifier to lter the con-
For example, the following code lters the post content before
displaying it:
<?php
use yii\helpers\HtmlPurifier;
?>
<div class="post">
<?= HtmlPurifier::process($post->text) ?>
</div>
Tip: While HTMLPurier does excellent job in making output
safe, it is not fast. You should consider caching the ltering 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 directory
@app/views/ControllerID
ControllerID refers to
PostController,
PostCommentController,
by default, where
the controller ID. For example, if the controller class is
@app/views/post; If it is
@app/views/post-comment. In case the controller
the directory would be views/ControllerID under
the directory would be
the directory would be
belongs to a module,
the
•
module directory.
For views rendered in a widget, they should be put under the
/views
directory by default, where
WidgetPath
WidgetPath
stands for the directory
containing the widget class le.
•
For views rendered by other objects, it is recommended that you follow
the similar convention as that for widgets.
yii\base
\ViewContextInterface::getViewPath() method of controllers or widgets.
You may customize these default view directories by overriding the
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
rendering method
* @param array $params the
* @return string rendering
*/
methodName($view, $params =
name or file path, depending on the actual
data to be passed to the view
result
[])
3.7.
VIEWS
87
Rendering in Controllers
Within controllers, you may call the following controller methods to render
views:
• render():
renders a named view and applies a layout to the rendering
result.
• renderPartial(): renders a named view without any layout.
• renderAjax(): renders a named view without any layout, and
injects
all registered JS/CSS scripts and les. It is usually used in response
to AJAX Web requests.
• renderFile():
renders a view specied in terms of a view le path or
alias.
• renderContent():
renders a static string by embedding it into the
currently applicable layout.
This method is available since version
2.0.1.
For example,
namespace app\controllers;
use
use
use
use
Yii;
app\models\Post;
yii\web\Controller;
yii\web\NotFoundHttpException;
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.
• render(): renders a named view.
• renderFile(): renders a view specied
alias.
For example,
namespace app\components;
in terms of a view le path or
88
CHAPTER 3.
APPLICATION STRUCTURE
use yii\base\Widget;
use yii\helpers\Html;
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
view component:
• render(): renders a named view.
• renderAjax(): renders a named view and injects all registered JS/CSS
scripts and les. It is usually used in response to AJAX Web requests.
• renderFile():
renders a view specied in terms of a view le path or
alias.
For example, the following code in a view renders the
_overview.php
view
le which is in the same directory as the view being currently rendered.
Remember that
$this
in a view refers to the
view
component:
<?= $this->render('_overview') ?>
Rendering in Other Places
In any place, you can get access to the
expression
Yii::$app->view
view
application component by the
and then call its aforementioned methods to ren-
der a view. For example,
// 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 le path/alias. In most cases, you would use the former because it
is more concise and exible. We call views specied using names as
views.
named
A view name is resolved into the corresponding view le path according
to the following rules:
3.7.
VIEWS
•
89
A view name may omit the le extension name. In this case,
used as the extension. For example, the view name
to the le name
•
.php will be
corresponds
about.php.
If the view name starts with double slashes
le path would be
under the
about
@app/views/ViewName.
application's view path.
//,
the corresponding view
That is, the view is looked for
For example,
@app/views/site/about.php.
• If the view name starts with a single slash /,
//site/about
will
be resolved into
by prexing the view name with the
the view le path is formed
view path of the currently active
@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 le path would be @app/views/user/create.php.
module. If there is no active module,
•
If the view is rendered with a context and the context implements yii
\base\ViewContextInterface, the view le path is formed by prexing the view path of the context to the view name. This mainly applies
to the views rendered within controllers and widgets.
about
the controller
•
@app/views/site/about.php
SiteController.
will be resolved into
For example,
if the context is
If a view is rendered within another view, the directory containing the
other view le will be prexed to the new view name to form the actual
@app/views/post
@app/views/post/index.php.
According to the above rules, calling $this->render('view') in a controller app
\controllers\PostController will actually render the view le @app/views/post/
view.php, while calling $this->render('_overview') in that view will render the
view le @app/views/post/_overview.php.
view le path. For example,
/item.php
item
will be resolved into
if it is being rendered in the view
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 methods, 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
objects accessible in views (e.g.
Yii::$app).
view component or other
Using the code below as an
example, within the view you can get the controller object by the expression
90
CHAPTER 3.
$this->context.
APPLICATION STRUCTURE
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 dierent
places.
Sharing Data among Views
The
view component provides the params property that you can use to share
data among views.
For example, in an
about
view, you can have the following code which
species the current segment of the breadcrumbs.
$this->params['breadcrumbs'][] = 'About Us';
Then, in the layout le, which is also a view, you can display the breadcrumbs
using the data passed along
params:
<?= yii\widgets\Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['
breadcrumbs'] : [],
]) ?>
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.
layouts.
/layouts
By default, layouts are stored in the directory
@app/views/
views
For layouts used within a module, they should be stored in the
directory under the
module directory. You may customize the
yii\base\Module::$layoutPath
default layout directory by conguring the
property of the application or modules.
The following example shows how a layout looks like.
Note that for
illustrative purpose, we have greatly simplied the code in the layout.
In
3.7.
VIEWS
91
practice, you may want to add more content to it, such as head tags, main
menu, etc.
<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @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>© 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
layout when
yii\base\Controller::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.
• beginPage():
the layout.
This method should be called at the very beginning of
It triggers the
EVENT_BEGIN_PAGE
event which indicates
the beginning of a page.
• endPage():
triggers the
• head():
This method should be called at the end of the layout. It
EVENT_END_PAGE
event which indicates the end of a page.
This method should be called within the
HTML page.
<head>
section of an
It generates a placeholder which will be replaced with
the registered head HTML code (e.g.
link tags, meta tags) when a
page nishes rendering.
• beginBody():
<body>
This method should be called at the beginning of the
section. It triggers the
EVENT_BEGIN_BODY
event and generates
a placeholder which will be replaced by the registered HTML code (e.g.
JavaScript) targeted at the body begin position.
92
CHAPTER 3.
• endBody():
APPLICATION STRUCTURE
This method should be called at the end of the
tion. It triggers the
<body>
sec-
EVENT_END_BODY event and generates a placeholder
which will be replaced by the registered HTML code (e.g. JavaScript)
targeted at the body end position.
Accessing Data in Layouts
Within a layout, you have access to two predened variables:
$content.
The former refers to the
view
$this
and
component, like in normal views,
while the latter contains the rendering result of a content view which is
render()
rendered by calling the
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
render() method in a controller, 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 dierent layout by conguring either
::$layout or yii\base\Controller::$layout.
yii\base\Application
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
@app/views/layouts/post.php
layout property is untouched,
@app/views/layouts/main.php as the layout.
controllers, assuming their
default
post
controller to use
as the layout when rendering its views.
Other
will still use the
namespace app\controllers;
use yii\web\Controller;
class PostController extends Controller
{
public $layout = 'post';
}
// ...
For controllers belonging to a module, you may also congure the module's
layout
property to use a particular layout for these controllers.
Because the
layout
property may be congured at dierent levels (con-
trollers, modules, application), behind the scene Yii takes two steps to determine what is the actual layout le being used for a particular controller.
In the rst step, it determines the layout value and the context module:
3.7.
VIEWS
•
If the
93
yii\base\Controller::$layout
property of the controller is
not null, use it as the layout value and the
module
of the controller as
the context module.
•
If
layout
is null, search through all ancestor modules (including the
application itself ) of the controller and nd the rst module whose
layout
property is not null.
Use that module and its
layout
value
as the context 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 le according to the layout
value and the context module determined in the rst step. The layout value
can be:
•
•
@app/views/layouts/main).
path (e.g. /main): the layout
a path alias (e.g.
an absolute
value starts with a slash.
The actual layout le will be looked for under the application's
path
layout
@app/views/layouts.
• a relative path (e.g. main): the actual layout le will be looked for under
the context module's layout path which defaults to the views/layouts
which defaults to
directory under the
•
the boolean value
module directory.
false:
no layout will be applied.
If the layout value does not contain a le extension, it will use the default
.php.
one
Nested Layouts
Sometimes you may want to nest one layout in another.
For example, in
dierent sections of a Web site, you want to use dierent layouts, while all
these layouts share the same basic layout that generates the overall HTML5
page structure. You can achieve this goal by calling
endContent()
beginContent()
and
in the child layouts like the following:
<?php $this->beginContent('@app/views/layouts/base.php'); ?>
...child layout content here...
<?php $this->endContent(); ?>
beginContent()
beginContent() species what
As shown above, the child layout content should be enclosed within
and
endContent().
The parameter passed to
is the parent layout. It can be either a layout le 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 dene a block in a content view and display it in the layout.
94
CHAPTER 3.
You call
beginBlock()
can then be accessed via
and
APPLICATION STRUCTURE
endBlock()
to dene a block.
$view->blocks[$blockID],
where
$blockID
The block
stands for
a unique ID that you assign to the block when dening it.
The following example shows how you can use blocks to customize specic
parts of a layout in a content view.
First, in a content view, dene one or multiple blocks:
...
<?php $this->beginBlock('block1'); ?>
...content of block1...
<?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 dened.
...
<?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.
VIEWS
95
3.7.4 Using View Components
View components
provides many view-related features. While you can get
view components by creating individual instances of
child class, in most cases you will mainly use the
nent.
yii\base\View
view
or its
application compo-
You can congure this component in application congurations like
the following:
[
// ...
'components' => [
'view' => [
'class' => 'app\components\View',
],
// ...
],
]
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,
15 , Smarty16 .
such as Twig
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.
the
title
To solve this problem,
yii\web\View
provides
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';
?>
15
16
http://twig.sensiolabs.org/
http://www.smarty.net/
96
CHAPTER 3.
APPLICATION STRUCTURE
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 dierent
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
yii\web\View::registerMetaTag() in a content view, like the
following:
<?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 nishes rendering. The
following HTML code will be generated and inserted at the place where you
call
yii\web\View::head()
in the layout:
<meta name="keywords" content="yii, framework, php">
Note that if you call
yii\web\View::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
::registerLinkTag().
yii\web\View
For example, in a content view, you can register a
link tag like follows,
$this->registerLinkTag([
'title' => 'Live News for Yii',
'rel' => 'alternate',
3.7.
]);
VIEWS
97
'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
registerMetaTags(), you can specify a key when calling registerLinkTag()
to avoid generating repeated link tags.
3.7.5 View Events
View components
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.
• EVENT_BEFORE_RENDER:
triggered at the beginning of rendering a le
in a controller. Handlers of this event may set
yii\base\ViewEvent
::$isValid to be false to cancel the rendering process.
• EVENT_AFTER_RENDER: triggered after rendering a le by the call of
yii\base\View::afterRender(). Handlers of this event may obtain
the rendering result through yii\base\ViewEvent::$output and may
modify this property to change the rendering result.
• EVENT_BEGIN_PAGE: triggered by the call of yii\base\View::beginPage()
in layouts.
• EVENT_END_PAGE:
triggered by the call of
yii\base\View::endPage()
in layouts.
• EVENT_BEGIN_BODY: triggered by the call of yii\web\View::beginBody()
in layouts.
• EVENT_END_BODY:
triggered by the call of
yii\web\View::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');
98
CHAPTER 3.
APPLICATION STRUCTURE
}
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
yii\web\ViewAction
standalone action called
in a controller. For example,
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public function actions()
{
return [
'page' => [
'class' => 'yii\web\ViewAction',
],
];
}
}
Now if you create a view named
pages,
about
under the directory
@app/views/site/
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
yii\web\ViewAction
which view is requested.
The action will then look for this view under the directory
/pages.
You may congure
@app/views/site
yii\web\ViewAction::$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.
header, footer).
page
3.8.
MODULES
•
99
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, controllers, 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 dier from applications 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
base path
module. Within the directory, there are sub-directories, such as
models, views,
of the
controllers,
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
controllers/
DefaultController.php
models/
views/
layouts/
default/
index.php
the module class file
containing controller class files
the default controller class file
containing model class files
containing controller view and layout files
containing layout view files
containing view files for DefaultController
the index view file
Module Classes
yii
\base\Module. The class should be located directly under the module's base
path and should be autoloadable. When a module is being accessed, a single
Each module should have a unique module class which extends from
instance of the corresponding module class will be created. 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:
namespace app\modules\forum;
class Module extends \yii\base\Module
{
public function init()
{
100
CHAPTER 3.
APPLICATION STRUCTURE
parent::init();
}
}
If the
$this->params['foo'] = 'bar';
// ... other initialization code ...
init()
method contains a lot of code initializing the module's proper-
ties, you may also save them in terms of a conguration 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 conguration le
config.php
may contain the following content,
similar to that in an application conguration.
<?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 controller
classes under the
ule class.
controllers
sub-namespace of the namespace of the mod-
This also means the controller class les should be put in the
controllers directory within the module's base path. For example, to
ate a post controller in the forum module shown in the last subsection,
creyou
should declare the controller class like the following:
namespace app\modules\forum\controllers;
use yii\web\Controller;
class PostController extends Controller
{
// ...
}
You may customize the namespace of controller classes by conguring the
yii\base\Module::$controllerNamespace
property.
In case some of the
controllers are outside of this namespace, you may make them accessible
3.8.
MODULES
101
yii\base\Module::$controllerMap
by conguring the
property, similar to
what you do in an application.
Views in Modules
Views in a module should be put in the
base path.
views
directory within the module's
For views rendered by a controller in the module, they should
be put under the directory
views/ControllerID,
ControllerID refers to
PostController, the
where
the controller ID. For example, if the controller class is
directory would be
views/post
within the module's
base path.
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
tory by default, and you should congure the
property to point to the layout name.
views/layouts direc-
yii\base\Module::$layout
If you do not congure the
layout
property, the application's layout will be used instead.
3.8.2 Using Modules
To use a module in an application, simply congure the application by listing
the module in the
modules
property of the application. The following code
in the application conguration uses the
[
]
The
forum
module:
'modules' => [
'forum' => [
'class' => 'app\modules\forum\Module',
// ... other configurations for the module ...
],
],
modules
property takes an array of module congurations. Each array
key represents a
module ID
which uniquely identies the module among all
modules in the application, and the corresponding array value is a conguration for creating the module.
Routes
Like accessing controllers in an application, routes are used to address controllers in a module. A route for a controller within a module must begin
with the module ID followed by the controller ID and action ID. For example, if an application uses a module named
/index
would represent the
index
action of
forum, then the route forum/post
the post controller in the mod-
ule. If the route only contains the module ID, then the
::$defaultRoute
property, which defaults to
default,
controller/action should be used. This means a route
the
default
controller in the
forum
module.
yii\base\Module
will determine which
forum
would represent
102
CHAPTER 3.
APPLICATION STRUCTURE
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
getInstance() method
interested in. The
of the module class that you are
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 dierent 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 xed 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 rst, and then
get the ID via
$module->id.
You may also access the instance of a module using the following approaches:
// get the child 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 rst 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 you have the module instance, you can access parameters and components registered with the module. For example,
$maxPostCount = $module->params['maxPostCount'];
Bootstrapping Modules
debug module is
in the bootstrap
Some modules may need to be run for every request. The
such an example. To do so, list the IDs of such modules
property of the application.
For example, the following application conguration makes sure the
module is always loaded:
[
'bootstrap' => [
'debug',
],
debug
3.8.
]
MODULES
103
'modules' => [
'debug' => 'yii\debug\Module',
],
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
in the
while the latter
modules
child module.
Child modules must be declared
property of their parent modules. For example,
namespace app\modules\forum;
class Module extends \yii\base\Module
{
public function init()
{
parent::init();
}
}
$this->modules = [
'admin' => [
// you should consider using a shorter namespace here!
'class' => 'app\modules\forum\modules\admin\Module',
],
];
For a controller within a nested module, its route should include the IDs of
forum/admin/dashboard/index
dashboard controller in the admin module
forum module.
all its ancestor modules. For example, the route
represents the
index
action of the
which is a child module of the
Info: The
getModule()
method only returns the child module
directly belonging to its parent. The
yii\base\Application::
$loadedModules property keeps a list of loaded modules, including both direct children and nested ones, indexed by their class
names.
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 specic 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-
104
CHAPTER 3.
APPLICATION STRUCTURE
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 example, an access control lter may run before actions to ensure that they are
allowed to be accessed by particular end users; a content compression lter
may run after actions to compress the response content before sending them
out to end users.
A lter may consist of a pre-lter (ltering logic applied
and/or a post-lter (logic applied
after
before
actions)
actions).
3.9.1 Using Filters
Filters are essentially a special kind of behaviors. Therefore, using lters is
the same as using behaviors. You can declare lters in a controller class by
overriding its
behaviors()
method like the following:
public function behaviors()
{
return [
[
'class' => 'yii\filters\HttpCache',
'only' => ['index', 'view'],
'lastModified' => function ($action, $params) {
$q = new \yii\db\Query();
return $q->from('user')->max('updated_at');
},
],
];
}
By default, lters declared in a controller class will be applied to
in that controller.
lter should be applied to by conguring the
example, the
all
actions
You can, however, explicitly specify which actions the
HttpCache
can also congure the
only
lter only applies to the
except
property. In the above
index
and
view
actions. You
property to blacklist some actions from being
ltered.
Besides controllers, you can also declare lters in a module or application.
When you do so, the lters will be applied to
all
controller actions belonging
to that module or application, unless you congure the lters'
except
only
properties like described above.
Note:
When declaring lters in modules or applications, you
should use routes instead of action IDs in the
only
and
except
properties. This is because action IDs alone cannot fully specify
actions within the scope of a module or application.
and
3.9.
FILTERS
105
When multiple lters are congured for a single action, they are applied
according to the rules described below:
•
Pre-ltering
Apply lters declared in the application in the order they are
listed in
behaviors().
Apply lters declared in the module in the order they are listed
in
behaviors().
Apply lters declared in the controller in the order they are listed
in
behaviors().
If any of the lters cancel the action execution, the lters (both
pre-lters and post-lters) after it will not be applied.
•
•
Running the action if it passes the pre-ltering.
Post-ltering
Apply lters declared in the controller in the reverse order they
are listed in
behaviors().
Apply lters declared in the module in the reverse order they are
listed in
behaviors().
Apply lters declared in the application in the reverse order they
are listed in
behaviors().
3.9.2 Creating Filters
yii\base\ActionFilter and overafterAction() methods. The former will
To create a new action lter, extend from
ride the
beforeAction()
and/or
be executed before an action runs while the latter after an action runs. The
return value of
beforeAction()
determines whether an action should be
executed or not. If it is false, the lters after this one will be skipped and
the action will not be executed.
The following example shows a lter that logs the action execution time:
namespace app\components;
use Yii;
use yii\base\ActionFilter;
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;
106
}
CHAPTER 3.
}
APPLICATION STRUCTURE
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 lters, found primarily under the
filters
yii\
namespace. In the following, we will briey introduce these lters.
AccessControl
AccessControl provides simple access control based on a set of
rules.
In
particular, before an action is executed, AccessControl will examine the listed
rules and nd the rst 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 yii\filters\AccessControl;
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'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 Authorization section.
3.9.
FILTERS
107
Authentication Method Filters
Authentication method lters are used to authenticate a user using various
methods, such as HTTP Basic Auth
all under the
yii\filters\auth
17 , OAuth 218 . These lter classes are
namespace.
The following example shows how you can use
yii\filters\auth\HttpBasicAuth
to authenticate a user using an access token based on HTTP Basic Auth
method.
Note that in order for this to work, your
must implement the
findIdentityByAccessToken()
user identity class
method.
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
return [
'basicAuth' => [
'class' => HttpBasicAuth::className(),
],
];
}
Authentication method lters are commonly used in implementing RESTful
APIs. For more details, please refer to the RESTful Authentication section.
ContentNegotiator
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.
In the following example, ContentNegotiator is congured to support
JSON and XML response formats, and English (United States) and German
languages.
use yii\filters\ContentNegotiator;
use yii\web\Response;
public function behaviors()
{
return [
[
'class' => ContentNegotiator::className(),
'formats' => [
'application/json' => Response::FORMAT_JSON,
'application/xml' => Response::FORMAT_XML,
],
'languages' => [
'en-US',
'de',
],
17
18
http://en.wikipedia.org/wiki/Basic_access_authentication
http://oauth.net/2/
108
}
CHAPTER 3.
];
APPLICATION STRUCTURE
],
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 being used as a lter.
For example, you may congure it in the
application conguration like the following:
use yii\filters\ContentNegotiator;
use yii\web\Response;
[
];
'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 rst format and language listed
in
formats
and
languages
will be used.
HttpCache
HttpCache implements client-side caching by utilizing the
Etag
Last-Modified
HTTP headers. For example,
use yii\filters\HttpCache;
public function behaviors()
{
return [
[
'class' => HttpCache::className(),
'only' => ['index'],
'lastModified' => function ($action, $params) {
$q = new \yii\db\Query();
return $q->from('user')->max('updated_at');
},
],
];
}
and
3.9.
FILTERS
109
Please refer to the HTTP Caching section for more details about using HttpCache.
PageCache
PageCache implements server-side caching of whole pages. In the following
index action to cache the whole page for
maximum 60 seconds or until the count of entries in the post table changes. It
example, PageCache is applied to the
also stores dierent versions of the page depending on the chosen application
language.
use yii\filters\PageCache;
use yii\caching\DbDependency;
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.
RateLimiter
RateLimiter implements a rate limiting algorithm based on the leaky bucket
19 . It is primarily used in implementing RESTful APIs. Please
algorithm
refer to the Rate Limiting section for details about using this lter.
VerbFilter
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.
19
http://en.wikipedia.org/wiki/Leaky_bucket
110
CHAPTER 3.
APPLICATION STRUCTURE
use yii\filters\VerbFilter;
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'index' => ['get'],
'view' => ['get'],
'create' => ['get', 'post'],
'update' => ['get', 'put', 'post'],
'delete' => ['post', 'delete'],
],
],
];
}
Cors
Cross-origin resource sharing CORS
20 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 particular, 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 denes a way in which the browser
and the server can interact to determine whether or not to allow the crossorigin request.
The
Cors filter
should be dened before Authentication / Authoriza-
tion lters to make sure the CORS headers will always be sent.
use yii\filters\Cors;
use yii\helpers\ArrayHelper;
public function behaviors()
{
return ArrayHelper::merge([
[
'class' => Cors::className(),
],
], parent::behaviors());
}
The Cors ltering could be tuned using the
cors
property.
• cors['Origin']:
array used to dene allowed origins. Can be ['*'] (ev['http://www.myserver.net', 'http://www.myotherserver.com'].
to ['*'].
eryone) or
Default
20
https://developer.mozilla.org/fr/docs/HTTP/Access_control_CORS
3.9.
FILTERS
111
• 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 specic ones ['X-Request-With']. Default to ['*
'].
• cors['Access-Control-Allow-Credentials']: dene if current request can
be made using credentials. Can be true, false or null (not set). Default
to null.
• cors['Access-Control-Max-Age']: dene lifetime of pre-ight request. Default to 86400.
For example, allowing CORS for origin : http://www.myserver.net with method
GET, HEAD and OPTIONS :
use yii\filters\Cors;
use yii\helpers\ArrayHelper;
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.
the
login
For example adding the
Access-Control-Allow-Credentials
for
action could be done like this :
use yii\filters\Cors;
use yii\helpers\ArrayHelper;
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,
]
112
}
CHAPTER 3.
APPLICATION STRUCTURE
]
],
], parent::behaviors());
3.10 Widgets
Widgets are reusable building blocks used in views to create complex and
congurable user interface elements in an object-oriented fashion. For example, 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 yii\jui\DatePicker;
?>
<?= DatePicker::widget(['name' => 'date']) ?>
There are a good number of widgets bundled with Yii, such as
menu,
active form,
jQuery UI widgets, Twitter Bootstrap widgets. In the following, we
will introduce the basic knowledge about widgets. 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
widget()
yii\base\Widget::
method to use a widget in a view. The method takes a congura-
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 congured to use the Russian language and keep the input in the
attribute of
from_date
$model.
<?php
use yii\jui\DatePicker;
?>
<?= 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
::end().
yii\base\Widget::begin() and yii\base\Widget
yii\widgets\ActiveForm
For example, the following code uses the
widget to generate a login form. The widget will generate the opening and
3.10.
WIDGETS
closing
<form>
113
tags at the place where
begin()
and
end()
are called, respec-
tively. Anything in between will be rendered as is.
<?php
use yii\widgets\ActiveForm;
use yii\helpers\Html;
?>
<?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(); ?>
yii\base\Widget::widget() which returns the rendering
widget, the method yii\base\Widget::begin() returns an in-
Note that unlike
result of a
stance of the widget which you can use to build the widget content.
3.10.2 Creating Widgets
yii\base\Widget and override the yii
yii\base\Widget::run() methods. Usu-
To create a widget, extend from
\base\Widget::init()
ally, the
init()
and/or
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
run().
HelloWidget
message property.
echoed or returned as a string by
In the following example,
content assigned to its
HTML-encodes and displays the
If the property is not set, it will
display Hello World by default.
namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;
class HelloWidget extends Widget
{
public $message;
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = 'Hello World';
}
}
114
}
CHAPTER 3.
APPLICATION STRUCTURE
public function run()
{
return Html::encode($this->message);
}
To use this widget, simply insert the following code in a view:
<?php
use app\components\HelloWidget;
?>
<?= HelloWidget::widget(['message' => 'Good morning']) ?>
Below is a variant of
begin()
and
end()
HelloWidget
which takes the content enclosed within the
calls, HTML-encodes it and then displays it.
namespace app\components;
use yii\base\Widget;
use yii\helpers\Html;
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's output buer is started in
between the calls of
in
init()
init() and run() can be captured,
so that any output
processed and returned
run().
Info: When you call
yii\base\Widget::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
\Widget::end(),
run() method
by end().
the
result will be echoed
The following code shows how to use this new variant of
<?php
use app\components\HelloWidget;
?>
<?php HelloWidget::begin(); ?>
yii\base
will be called whose return
HelloWidget:
3.11.
ASSETS
115
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
it in a view and call
run()
method, a better approach is to put
yii\base\Widget::render() to render it.
For example,
public function run()
{
return $this->render('hello');
}
By default, views for a widget should be stored in les in the
/views
directory, where
widget class le.
WidgetPath
WidgetPath
stands for the directory containing the
Therefore, the above example will render the view le
@app/components/views/hello.php, assuming the widget class is located under
@app/components. You may override the yii\base\Widget::getViewPath()
method to customize the directory containing the widget view les.
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 dierence is that a widget is a redistributable
class, while a view is just a plain PHP script that you would prefer to keep
within your application.
3.11 Assets
An asset in Yii is a le that may be referenced in a Web page. It can be a
CSS le, a JavaScript le, an image or video le, 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
yii\jui\DatePicker
widget in a page, it will automat-
ically include the required CSS and JavaScript les, instead of asking you
to manually nd these les and include them. And when you upgrade the
widget to a new version, it will automatically use the new version of the
116
CHAPTER 3.
APPLICATION STRUCTURE
asset les. 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 les in the bundle in the
rendered Web page.
3.11.2 Dening Asset Bundles
Asset bundles are specied as PHP classes extending from
yii\web\AssetBundle.
The name of a bundle is simply its corresponding fully qualied PHP class
name (without the leading backslash). An asset bundle class should be autoloadable. It usually species where the assets are located, what CSS and
JavaScript les the bundle contains, and how the bundle depends on other
bundles.
The following code denes the main asset bundle used by the basic project
template:
<?php
namespace app\assets;
use yii\web\AssetBundle;
class AppAsset extends AssetBundle
{
public $basePath = '@webroot';
public $baseUrl = '@web';
public $css = [
'css/site.css',
];
public $js = [
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
}
The above
@webroot
AppAsset
class species that the asset les are located under the
directory which corresponds to the URL
single CSS le
css/site.css and no JavaScript le;
other bundles:
yii\web\YiiAsset
and
@web;
yii\bootstrap\BootstrapAsset.
yii\web\AssetBundle
More detailed explanation about the properties of
can be found in the following:
the bundle contains a
the bundle depends on two
3.11.
ASSETS
117
• sourcePath:
species the root directory that contains the asset les
in this bundle. This property should be set if the root directory is not
Web accessible. Otherwise, you should set the
basePath
property and
baseUrl, instead. Path aliases can be used here.
• basePath: species a Web-accessible directory that contains the asset
les in this bundle. When you specify the 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 les are already in a Web-accessible directory
and do not need asset publishing. Path aliases can be used here.
• baseUrl: species the URL corresponding to the directory basePath.
Like basePath, if you specify the sourcePath property, the asset manager will publish the assets and overwrite this property accordingly.
Path aliases can be used here.
• js:
an array listing the JavaScript les contained in this bundle. Note
that only forward slash / should be used as directory separators. Each
JavaScript le can be specied in one of the following two formats:
a relative path representing a local JavaScript le (e.g.
js).
js/main.
The actual path of the le can be determined by prepending
yii\web\AssetManager::$basePath to the relative path, and the
actual URL of the le can be determined by prepending yii\web
\AssetManager::$baseUrl to the relative path.
an absolute URL representing an external JavaScript le. For ex-
http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min
//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js.
ample,
• css:
.js
or
an array listing the CSS les contained in this bundle. The format
of this array is the same as that of
• depends:
js.
an array listing the names of the asset bundles that this
bundle depends on (to be explained shortly).
• jsOptions: species the options that will be passed to the yii\web
\View::registerJsFile() method when it is called to register every
JavaScript le in this bundle.
• cssOptions: species the options that will be passed to the yii\web
\View::registerCssFile() method when it is called to register every
CSS le in this bundle.
• publishOptions: species the options that will be passed to the yii
\web\AssetManager::publish() method when it is called to publish
source asset les to a Web directory. This is only used if you specify
the
sourcePath
property.
Asset Locations
Assets, based on their location, can be classied as:
•
source assets: the asset les are located together with PHP source code
118
CHAPTER 3.
APPLICATION STRUCTURE
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 les are located in a Web directory and can
thus be directly accessed via Web.
•
external assets:
the asset les are located on a Web server that is
dierent from the one hosting your Web application.
When dening an asset bundle class, if you specify the
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
basePath
and
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 species
basePath instead of 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
sourcePath
property when dening asset bundle classes for them.
Note: Do not use
as the source path. This diasset manager to save the asset
@webroot/assets
rectory is used by default by the
les published from their source location. Any content in this directory is considered temporarily and may be subject to removal.
Asset Dependencies
When you include multiple CSS or JavaScript les in a Web page, they have
to follow a certain order 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 le is included before the jQuery UI JavaScript le. We call such
ordering the dependencies among assets.
Asset dependencies are mainly specied through the
::$depends property.
In the
two other asset bundles:
AppAsset
yii\web\AssetBundle
example, the asset bundle depends on
yii\web\YiiAsset and yii\bootstrap\BootstrapAsset,
which means the CSS and JavaScript les in
AppAsset
will be included
after
those les 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
cssOptions and jsOptions properties to customize the
way that CSS and JavaScript les are included in a page.
these properties will be passed to the
The values of
yii\web\View::registerCssFile()
3.11.
and
ASSETS
yii\web\View::registerJsFile()
119
methods, respectively, when they
are called by the view to include CSS and JavaScript les.
Note: The options you set in a bundle class apply to
every
CSS/-
JavaScript le in the bundle. If you want to use dierent options
for dierent les, you should create separate asset bundles, and
use one set of options in each bundle.
For example, to conditionally include a CSS le for browsers that are IE9 or
below, you can use the following option:
public $cssOptions = ['condition' => 'lte IE9'];
This will cause a CSS le in the bundle to be included using the following
HTML tags:
<!--[if lte IE9]>
<link rel="stylesheet" href="path/to/foo.css">
<![endif]-->
To wrap the generated CSS link tags within
cssOptions
<noscript>,
you can congure
as follows,
public $cssOptions = ['noscript' => true];
To include a JavaScript le in the head section of a page (by default, JavaScript
les are included at the end of the body section), use the following option:
public $jsOptions = ['position' => \yii\web\View::POS_HEAD];
By default, when an asset bundle is being published, all contents in the di-
yii\web\AssetBundle::$sourcePath will be published.
publishOptions property. For example, to publish only one or a few subdirectories of yii\web
\AssetBundle::$sourcePath, you can do the following in the asset bundle
rectory specied by
You can customize this behavior by conguring the
class:
<?php
namespace app\assets;
use yii\web\AssetBundle;
class FontAwesomeAsset extends AssetBundle
{
public $sourcePath = '@bower/font-awesome';
public $css = [
'css/font-awesome.min.css',
];
public function init()
{
parent::init();
$this->publishOptions['beforeCopy'] = function ($from, $to) {
$dirname = basename(dirname($from));
120
CHAPTER 3.
}
}
};
APPLICATION STRUCTURE
return $dirname === 'fonts' || $dirname === 'css';
21 .
The above example denes an asset bundle for the fontawesome package
By specifying the
beforeCopy
publishing option, only the
fonts
and
css
sub-
directories will be published.
Bower and NPM Assets
22 and/or NPM23 . If
Most JavaScript/CSS packages are managed by Bower
your application or extension is using such a package, it is recommended
that you follow these steps to manage the assets in the library:
composer.json le 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
1. Modify the
refer to the library.
2. Create an asset bundle class and list the JavaScript/CSS les that you
plan to use in your application or extension. You should specify the
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 les in a subdirectory. If this is the case, you should specify the subdirectory
as the value of
uses
sourcePath.
@bower/jquery/dist
For example,
instead of
yii\web\JqueryAsset
@bower/jquery.
3.11.3 Using Asset Bundles
To use an asset bundle, register it with a view by calling the
\AssetBundle::register()
yii\web
method. For example, in a view template you
can register an asset bundle like the following:
use app\assets\AppAsset;
AppAsset::register($this);
Info: The
// $this represents the view object
yii\web\AssetBundle::register()
method returns
an asset bundle object containing the information about the published assets, such as
21
http://fontawesome.io/
http://bower.io/
23
https://www.npmjs.org/
22
basePath
or
baseUrl.
3.11.
ASSETS
121
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 scenes 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
directory. Later, when the view renders a page, it will generate
<script>
<link>
and
tags for the CSS and JavaScript les 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
::$css
and
yii\web\AssetBundle::$js
yii\web\AssetBundle
properties.
Customizing Asset Bundles
Yii manages asset bundles through an application component named
assetManager
yii\web\AssetManager. By conguring the yii
\web\AssetManager::$bundles property, it is possible to customize the behavior of an asset bundle. For example, the default yii\web\JqueryAsset
which is implemented by
asset bundle uses the
jquery.js
le 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 conguring
assetManager
in the
application conguration like the following:
return [
// ...
'components' => [
'assetManager' => [
'bundles' => [
'yii\web\JqueryAsset' => [
'sourcePath' => null,
// do not publish the bundle
'js' => [
'//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery
.min.js',
]
],
],
],
],
];
You can congure multiple asset bundles similarly through
::$bundles.
yii\web\AssetManager
The array keys should be the class names (without the leading
backslash) of the asset bundles, and the array values should be the corresponding conguration arrays.
Tip: You can conditionally choose which assets to use in an asset
bundle. The following example shows how to use
development environment and
jquery.min.js
jquery.js
otherwise:
in the
122
CHAPTER 3.
APPLICATION STRUCTURE
'yii\web\JqueryAsset' => [
'js' => [
YII_ENV_DEV ? 'jquery.js' : 'jquery.min.js'
]
],
You can disable one or multiple asset bundles by associating
names of the asset bundles that you want to disable.
false
with the
When you register
a disabled asset bundle with a view, none of its dependent bundles will be
registered, and the view also will not include any of the assets in the bundle
in the page it renders. For example, to disable
yii\web\JqueryAsset,
you
can use the following conguration:
return [
// ...
'components' => [
'assetManager' => [
'bundles' => [
'yii\web\JqueryAsset' => false,
],
],
],
];
You can also disable
$bundles
as
all
asset bundles by setting
yii\web\AssetManager::
false.
Asset Mapping
Sometimes you may want to x incorrect/incompatible asset le paths used
in multiple asset bundles. For example, bundle A uses
1.11.1, and bundle B uses
jquery.js
version 2.1.1.
jquery.min.js
version
While you can x 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, congure the
yii\web\AssetManager::$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
assetMap
are the asset names that you want to x, and the
values are the desired asset paths. When you register an asset bundle with a
view, each relative asset le in its
css and js arrays will be examined against
3.11.
ASSETS
this map.
123
If any of the keys are found to be the last part of an asset le
(which is prexed with
yii\web\AssetBundle::$sourcePath
if available),
the corresponding value will replace the asset and be registered with the view.
For example, the asset le
my/path/to/jquery.js
matches the key
jquery.js.
Note: Only assets specied using relative paths are subject to
asset mapping. The target asset paths should be either absolute
URLs or paths relative to
yii\web\AssetManager::$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
is done automatically by the
asset manager.
By default, assets are published to the directory
corresponds to the URL
conguring the
basePath
@web/assets.
and
baseUrl
asset publishing,
@webroot/assets
and
which
You may customize this location by
properties.
Instead of publishing assets by le copying, you may consider using symbolic links, if your OS and Web server allow. This feature can be enabled by
setting
linkAssets
to be true.
return [
// ...
'components' => [
'assetManager' => [
'linkAssets' => true,
],
],
];
With the above conguration, 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 le copying and can also ensure that the published assets are
always up-to-date.
Cache Busting
For Web application running in production mode, it is a common practice
to enable HTTP caching for assets and other static resources. A drawback
of this practice is that whenever you modify an asset and deploy it to production, a user client may still use the old version due to the HTTP caching.
To overcome this drawback, you may use the cache busting feature, which
was introduced in version 2.0.3, by conguring
the following:
return [
// ...
yii\web\AssetManager
like
124
];
CHAPTER 3.
APPLICATION STRUCTURE
'components' => [
'assetManager' => [
'appendTimestamp' => true,
],
],
By doing so, the URL of every published asset will be appended with its
last modication timestamp. For example, the URL to yii.js may look like
/assets/5515a87c/yii.js?v=1423448645", where the parameter v represents the
last modication timestamp of the yii.js le. Now if you modify an asset, its
URL will be changed, too, which causes the client to fetch the latest version
of the asset.
3.11.4 Commonly Used Asset Bundles
The core Yii code has dened many asset bundles. Among them, the following bundles are commonly used and may be referenced in your application
or extension code.
• yii\web\YiiAsset:
It mainly includes the
yii.js le 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.
• yii\web\JqueryAsset:
It includes the
jquery.js
le from the jQuery
Bower package.
• yii\bootstrap\BootstrapAsset:
It includes the CSS le from the
Twitter Bootstrap framework.
• yii\bootstrap\BootstrapPluginAsset:
It includes the JavaScript
le from the Twitter Bootstrap framework for supporting Bootstrap
JavaScript plugins.
• yii\jui\JuiAsset:
It includes the CSS and JavaScript les from the
jQuery UI library.
If your code depends on jQuery, jQuery UI or Bootstrap, you should use
these predened 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
24 or SCSS25 ;
CSS/JavaScript. For example, for CSS code you may use LESS
and for JavaScript you may use TypeScript
24
http://lesscss.org/
http://sass-lang.com/
26
http://www.typescriptlang.org/
25
26 .
3.11.
ASSETS
125
You can list the asset les in extended syntax in the
css and js properties
of 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 = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
}
When you register such an asset bundle with a view, the
asset manager
will automatically run the pre-processor tools to convert assets in recognized
extended syntax into CSS/JavaScript. When the view nally renders a page,
it will include the CSS/JavaScript les in the page, instead of the original
assets in extended syntax.
Yii uses the le name extensions to identify which extended syntax an
asset is in. By default it recognizes the following syntax and le name extensions:
•
•
•
•
•
LESS
27 : .less
28
SCSS : .scss
29
Stylus : .styl
30 : .coffee
CoeeScript
31
TypeScript : .ts
Yii relies on the installed pre-processor tools to convert assets. For example,
to use LESS
32 you should install the lessc pre-processor command.
You can customize the pre-processor commands and the supported extended syntax by conguring
yii\web\AssetManager::$converter like the
following:
return [
'components' => [
'assetManager' => [
'converter' => [
'class' => 'yii\web\AssetConverter',
'commands' => [
27
http://lesscss.org/
http://sass-lang.com/
29
http://learnboost.github.io/stylus/
30
http://coffeescript.org/
31
http://www.typescriptlang.org/
32
http://lesscss.org/
28
126
CHAPTER 3.
],
];
],
],
],
APPLICATION STRUCTURE
'less' => ['css', 'lessc {from} {to} --no-color'],
'ts' => ['js', 'tsc --out {to} {from}'],
In the above, we specify the supported extended syntax via the
\AssetConverter::$commands
yii\web
property. The array keys are the le exten-
sion names (without leading dot), and the array values are the resulting asset
le 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 le paths and the target asset le paths.
Info: There are other ways of working with assets in extended
syntax, besides the one described above. For example, you can
33 to monitor and automatically
use build tools such as grunt
convert assets in extended syntax. In this case, you should list
the resulting CSS/JavaScript les in asset bundles rather than
the original les.
3.11.6 Combining and Compressing Assets
A Web page can include many CSS and/or JavaScript les. To reduce the
number of HTTP requests and the overall download size of these les, a
common practice is to combine and compress multiple CSS/JavaScript les
into one or very few les, and then include these compressed les 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 les is often more convenient
for debugging purposes.
In the following, we introduce an approach to combine and compress asset
les without the need to modify your existing application code.
1. Find all the 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 les in each group into a single le. Do
this similarly for the JavaScript les.
33
http://gruntjs.com/
3.11.
ASSETS
127
4. Dene a new asset bundle for each group:
•
Set the
css and js properties to be the combined CSS and JavaScript
les, respectively.
•
css
js properties to be empty, and setting their depends property
Customize the asset bundles in each group by setting their
and
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
les 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
bundles A, B and C, while Page Y uses asset bundles B, C and D.
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 in Group X, D
in Group Y, and (B, C) in Group S. Which one is better?
It depends.
The rst way has the advantage that both pages share the same combined
CSS and JavaScript les, which makes HTTP caching more eective.
On
the other hand, because the single group contains all bundles, the size of
the combined CSS and JavaScript les will be bigger and thus increase the
initial le transmission time. For simplicity in this example, we will use the
rst 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 trac data of various assets on dierent pages. At the beginning, you may start
with a single group for simplicity.
Use existing tools (e.g. Closure Compiler
34 , YUI Compressor35 ) to combine
and compress CSS and JavaScript les in all the bundles. Note that the les
should be combined in the order that satises 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 les starting from C and D, followed
by B and nally A.
After combining and compressing, we get one CSS le and one JavaScript
le. 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 le name unique to avoid
HTTP caching problems.
34
35
https://developers.google.com/closure/compiler/
https://github.com/yui/yuicompressor/
128
CHAPTER 3.
APPLICATION STRUCTURE
We are at the last step now. Congure the
asset manager
as follows in
the application conguration:
return [
'components' => [
'assetManager' => [
'bundles' => [
'all' => [
'class' => 'yii\web\AssetBundle',
'basePath' => '@webroot/assets',
'baseUrl' => '@web/assets',
'css' => ['all-xyz.css'],
'js' => ['all-xyz.js'],
],
'A' => ['css' => [], 'js' => [], 'depends'
'B' => ['css' => [], 'js' => [], 'depends'
'C' => ['css' => [], 'js' => [], 'depends'
'D' => ['css' => [], 'js' => [], 'depends'
],
],
],
];
=>
=>
=>
=>
['all']],
['all']],
['all']],
['all']],
As explained in the Customizing Asset Bundles subsection, the above conguration changes the default behavior of each bundle. In particular, Bundle
A, B, C and D no longer have any asset les.
the
all
bundle which contains the combined
They now all depend on
all-xyz.css
and
all-xyz.js
les.
Consequently, for Page X, instead of including the original source les from
Bundle A, B and C, only these two combined les will be included; the same
thing happens to Page Y.
There is one nal trick to make the above approach work more smoothly.
Instead of directly modifying the application conguration le, you may put
the bundle customization array in a separate le and conditionally include
this le in the application conguration. For example,
return [
'components' => [
'assetManager' => [
'bundles' => require(__DIR__ . '/' . (YII_ENV_PROD ? 'assetsprod.php' : 'assets-dev.php')),
],
],
];
That is, the asset bundle conguration array is saved in
production mode, and
Using the
asset
assets-dev.php
assets-prod.php
for
for non-production mode.
Command
Yii provides a console command named
we just described.
asset
to automate the approach that
3.11.
ASSETS
129
To use this command, you should rst create a conguration le 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 rst and then modify it to t for your needs.
yii asset/template assets.php
The command generates a le named
assets.php
in the current directory.
The content of this le 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
}',
// The list of asset bundles to compress:
'bundles' => [
// 'yii\web\YiiAsset',
// 'yii\web\JqueryAsset',
],
// Asset bundle for compression output:
'targets' => [
'all' => [
'class' => 'yii\web\AssetBundle',
'basePath' => '@webroot/assets',
'baseUrl' => '@web/assets',
'js' => 'js/all-{hash}.js',
'css' => 'css/all-{hash}.css',
],
],
// Asset manager configuration:
'assetManager' => [
],
];
You should modify this le 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 dene them in the
conguration.
130
CHAPTER 3.
APPLICATION STRUCTURE
JavaScript les are combined, compressed and written to
js/all-{hash}.js
where {hash} is replaced with the hash of the resulting le.
The
jsCompressor and cssCompressor options specify the console commands
or PHP callbacks for performing JavaScript and CSS combining/compress-
36 for combining JavaScript les
ing. By default, Yii uses Closure Compiler
and YUI Compressor
37 for combining CSS les. You should install those
tools manually or adjust these options to use your favorite tools.
With the conguration le, you can run the
asset
command to combine
and compress the asset les and then generate a new asset bundle conguration le
assets-prod.php:
yii asset assets.php config/assets-prod.php
The generated conguration le can be included in the application conguration, 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
38 to achieve the same goal.
excellent task runner tool grunt
Grouping Asset Bundles
In the last subsection, we have explained how to combine all asset bundles
into a single one in order to minimize the HTTP requests for asset les
referenced in an application. This is not always desirable in practice. For
example, imagine your application has a front end as well as a back end,
each of which uses a dierent set of JavaScript and CSS les. In this case,
combining all asset bundles from both ends into a single one does not make
sense, because the asset bundles for the front end are not used by the back
end and it would be a waste of network bandwidth to send the back end
assets when a front end page is requested.
To solve the above problem, you can divide asset bundles into groups and
combine asset bundles for each group. The following conguration shows how
you can group asset bundles:
return [
...
// Specify output bundles with groups:
'targets' => [
'allShared' => [
'js' => 'js/all-shared-{hash}.js',
'css' => 'css/all-shared-{hash}.css',
'depends' => [
// Include all assets shared between 'backend' and 'frontend
'
36
https://developers.google.com/closure/compiler/
https://github.com/yui/yuicompressor/
38
http://gruntjs.com/
37
3.12.
EXTENSIONS
131
'yii\web\YiiAsset',
'app\assets\SharedAsset',
];
],
...
],
],
'allBackEnd' => [
'js' => 'js/all-{hash}.js',
'css' => 'css/all-{hash}.css',
'depends' => [
// Include only 'backend' assets:
'app\assets\AdminAsset'
],
],
'allFrontEnd' => [
'js' => 'js/all-{hash}.js',
'css' => 'css/all-{hash}.css',
'depends' => [], // Include all remaining assets
],
allShared,
allFrontEnd. They each depends on an appropriate set of asset
example, allBackEnd depends on app\assets\AdminAsset. When
As you can see, the asset bundles are divided into three groups:
allBackEnd
and
bundles. For
running
asset command with this conguration,
it will combine asset bundles
according to the above specication.
Info: You may leave the
depends
conguration empty for one of
the target bundle. By doing so, that particular asset bundle will
depend on all of the remaining asset bundles that other target
bundles do not depend on.
3.12 Extensions
Extensions are redistributable software packages specically 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-specic software
packages. For general purpose software packages that can be used
without Yii, we will refer to them using the term package or
library.
132
CHAPTER 3.
APPLICATION STRUCTURE
3.12.1 Using Extensions
To use an extension, you need to install it rst.
Most extensions are dis-
39 packages which can be installed by taking the fol-
tributed as Composer
lowing two simple steps:
1. modify the
composer.json
le of your application and specify which ex-
tensions (Composer packages) you want to install.
2. run
composer install
to install the specied extensions.
40 if you do not have it.
41 - the
By default, Composer installs packages registered on Packagist
Note that you may need to install Composer
biggest repository for open source Composer packages.
extensions on Packagist.
You can look for
You may also create your own repository
congure Composer to use it.
42 and
This is useful if you are developing private
extensions that you want to share within your projects only.
Extensions installed by Composer are stored in the
tory, where
BasePath
BasePath/vendor
direc-
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
composer.json
{
yiisoft/yii2-imagine
extension, modify your
like the following:
// ...
"require": {
// ... other dependencies
}
}
"yiisoft/yii2-imagine": "*"
yiisoft/yii2-imagine under
directory imagine/imagine which
After the installation, you should see the directory
BasePath/vendor.
You should also see another
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
43 and named like yiisoft/yii2-xyz, where xyz
hosted on Packagist
varies for dierent extensions.
39
https://getcomposer.org/
https://getcomposer.org/
41
https://packagist.org/
42
https://getcomposer.org/doc/05-repositories.md#repository
43
https://packagist.org/
40
3.12.
EXTENSIONS
133
Now you can use the installed extensions like they are part of your application. The following example shows how you can use the
class provided by the
yiisoft/yii2-imagine
yii\imagine\Image
extension:
use Yii;
use yii\imagine\Image;
// 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 autoloader.
Installing Extensions Manually
In some rare occasions, you may want to install some or all extensions manually, rather than relying on Composer. To do so, you should:
1. download the extension archive les 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 standard
44 , 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
extension root directory. For example, assuming you have installed an extension in the directory
under the
myext
vendor/mycompany/myext,
and the extension classes are
namespace, then you can include the following code in your
application conguration:
[
'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.
44
http://www.php-fig.org/psr/psr-4/
134
CHAPTER 3.
APPLICATION STRUCTURE
It is recommended that you create an extension in terms of a Composer
package
45 so that it can be more easily installed and used by other users, as
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,
46 . The development and maintenance work for the
such as github.com
extension should be done on this repository.
2. Under the root directory of the project, create a le named
json
composer.
as required by Composer. Please refer to the next subsection for
more details.
3. Register your extension with a Composer repository, such as Packagist
47 , so that other users can nd and install your extension using
Composer.
composer.json
Each Composer package must have a
composer.json
le in its root directory.
The le contains the metadata about the package. You may nd complete
48 . The following exam-
specication about this le in the Composer Manual
ple shows the
{
composer.json
le for the
yiisoft/yii2-imagine
extension:
// package name
"name": "yiisoft/yii2-imagine",
// 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",
45
https://getcomposer.org/
https://github.com
47
https://packagist.org/
48
https://getcomposer.org/doc/01-basic-usage.md#composer-json-project-setup
46
3.12.
],
EXTENSIONS
}
135
"email": "
[email protected]"
// package dependencies
"require": {
"yiisoft/yii2": "*",
"imagine/imagine": "v0.5.0"
},
}
// class autoloading specs
"autoload": {
"psr-4": {
"yii\\imagine\\": ""
}
}
Package Name
Each Composer package should have a package name
which uniquely identies the package among all others. The format of package names is vendorName/projectName.
yiisoft/yii2-imagine, the vendor name
yii2-imagine, respectively.
Do NOT use
yiisoft
For example, in the package name
and the project name are
yiisoft
and
as your vendor name as it is reserved for use by the
Yii core code.
We recommend you prex
yii2-
to the project name for packages repre-
senting 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
extension as
It is important that you specify the package type of your
yii2-extension
so that the package can be recognized as a Yii
extension when being installed.
When a user runs composer install to install an extension, the
/yiisoft/extensions.php will be automatically updated to include
le
vendor
the infor-
mation about the new extension. From this le, Yii applications can know
which extensions are installed (the information can be accessed via
\Application::$extensions).
Dependencies
yii\base
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.
extension is released in a stable version.
Use stable dependencies when your
136
CHAPTER 3.
APPLICATION STRUCTURE
49 and/or NPM50 ,
Most JavaScript/CSS packages are managed using Bower
51 to enable maninstead of Composer. Yii uses the Composer asset plugin
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-
bower-asset/PackageName to refer to a Bower packcomposer.json, and use npm-asset/PackageName to refer to a NPM package.
age. In general, you can use
age in
When Composer installs a Bower or NPM package, by default the package
content will be installed under the
/Packages
@vendor/bower/PackageName
and
@vendor/npm
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
le, like shown below:
// ....
"autoload": {
"psr-4": {
"yii\\imagine\\": ""
}
}
}
You may list one or multiple root namespaces and their corresponding le
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
to an alias named
49
autoload
declaration will correspond
@yii/imagine.
http://bower.io/
https://www.npmjs.org/
51
https://github.com/francoispluchino/composer-asset-plugin
50
3.12.
EXTENSIONS
137
Recommended Practices
Because extensions are meant to be used by other people, you often need to
make an extra eort during development. 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
52 or PSR-0 standard53 .
your extension by following the PSR-4 standard
Your class namespaces should start with
extensionName
vendorName\extensionName,
where
is similar to the project name in the package name except that
yii2- prex. For example, for the yiisoft/yii2yii\imagine as the namespace for its classes.
yii, yii2 or yiisoft as your vendor name. These names are
it should not contain the
imagine
extension, we use
Do not use
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 application. 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
implementing
yii\base\BootstrapInterface.
bootstrapping class
by
For example,
namespace myname\mywidget;
use yii\base\BootstrapInterface;
use yii\base\Application;
class MyBootstrapClass implements BootstrapInterface
{
public function bootstrap($app)
{
$app->on(Application::EVENT_BEFORE_REQUEST, function () {
// do something here
});
}
}
You then list this class in the
{
composer.json
le of your extension like follows,
// ...
"extra": {
"bootstrap": "myname\\mywidget\\MyBootstrapClass"
52
53
http://www.php-fig.org/psr/psr-4/
http://www.php-fig.org/psr/psr-0/
138
}
CHAPTER 3.
APPLICATION STRUCTURE
}
When the extension is installed in an application, Yii will automatically
bootstrap()
instantiate the bootstrapping class and call its
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
and see how it declares and uses the
db
yii\caching\DbCache
class
property.
If your extension needs to create specic DB tables or make changes to
DB schema, you should
•
provide migrations to manipulate DB schema, rather than using plain
SQL les;
•
•
try to make the migrations applicable to dierent 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 les 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 les
directly accessible via Web:
•
ask users of the extension to manually copy the asset les to a specic
Web-accessible folder;
•
declare an asset bundle and rely on the asset publishing mechanism
to automatically copy the les listed in the asset bundle to a Webaccessible 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 dierent 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.
3.12.
•
EXTENSIONS
139
If the extension displays numbers, dates, etc., they should be formatted
using
yii\i18n\Formatter
with appropriate formatting rules.
For more details, please refer to the Internationalization section.
Testing
You want your extension to run awlessly 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 extension 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
ber (e.g.
You should give each release of your extension a version num-
1.0.1).
We recommend you follow the semantic versioning
54 prac-
tice when determining what version numbers should be used.
Releasing
To let other people know about your extension, you need to
release it to the public.
If it is the rst time you are releasing an extension, you should register
55 . After that, all you need
it on a Composer repository, such as Packagist
to do is simply create a release tag (e.g.
v1.0.1)
on the VCS repository of
your extension and notify the Composer repository about the new release.
People will then be able to nd the new release, and install or update the
extension through the Composer repository.
In the releases of your extension, in addition to code les, you should
also consider including the following to help other people learn about and
use your extension:
•
A readme le in the package root directory: it describes what your
extension does and how to install and use it. We recommend you write
56 format and name the le as readme.md.
it in Markdown
•
A changelog le in the package root directory: it lists what changes
are made in each release. The le may be written in Markdown format
and named as
•
changelog.md.
An upgrade le in the package root directory: it gives the instructions
on how to upgrade from older releases of the extension. The le 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 le.
54
http://semver.org
https://packagist.org/
56
http://daringfireball.net/projects/markdown/
55
140
CHAPTER 3.
•
APPLICATION STRUCTURE
API documentation: your code should be well documented to allow
other people to more easily read and understand it. You may refer to
57 to learn how to document your code.
the Object class le
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.
58 .
You may refer to the core framework
code style
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 Packagist
59 and can
be easily installed as described in the Using Extensions subsection.
•
60 : provides an extensible and high-performance API
yiisoft/yii2-apidoc
documentation generator. It is also used to generate the core framework API documentation.
•
61 : provides a set of commonly used auth clients,
yiisoft/yii2-authclient
such as Facebook OAuth2 client, GitHub OAuth2 client.
•
yiisoft/yii2-bootstrap
62 : provides a set of widgets that encapsulate the
•
63 components and plugins.
64
yiisoft/yii2-codeception : provides testing support based on Codecep65
tion .
•
yiisoft/yii2-debug
Bootstrap
66 : 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-elasticsearch
67 : provides the support for using Elasticsearch68 .
It includes basic querying/search support and also implements the Active Record pattern that allows you to store active records in Elasticsearch.
57
https://github.com/yiisoft/yii2/blob/master/framework/base/Object.php
https://github.com/yiisoft/yii2/wiki/Core-framework-code-style
59
https://packagist.org/
60
https://github.com/yiisoft/yii2-apidoc
61
https://github.com/yiisoft/yii2-authclient
62
https://github.com/yiisoft/yii2-bootstrap
63
http://getbootstrap.com/
64
https://github.com/yiisoft/yii2-codeception
65
http://codeception.com/
66
https://github.com/yiisoft/yii2-debug
67
https://github.com/yiisoft/yii2-elasticsearch
68
http://www.elasticsearch.org/
58
3.12.
•
EXTENSIONS
141
yiisoft/yii2-faker
69 : provides the support for using Faker70 to generate
fake data for you.
•
yiisoft/yii2-gii
71 : provides a Web-based code generator that is highly
extensible and can be used to quickly generate models, forms, modules,
CRUD, etc.
•
•
72 : provides commonly used image manipulation
73
functions based on Imagine .
74
yiisoft/yii2-jui : provides a set of widgets that encapsulate the JQuery
yiisoft/yii2-imagine
75 interactions and widgets.
76
77
yiisoft/yii2-mongodb : provides the support for using MongoDB .
UI
•
It includes features such as basic query, Active Record, migrations,
caching, code generation, etc.
•
yiisoft/yii2-redis
78 : provides the support for using redis79 . It includes
features such as basic query, Active Record, caching, etc.
•
•
yiisoft/yii2-smarty
80 : provides a template engine based on Smarty81 .
82
83
yiisoft/yii2-sphinx : provides the support for using Sphinx . It in-
cludes features such as basic query, Active Record, code generation,
etc.
69
•
yiisoft/yii2-swiftmailer
•
yiisoft/yii2-twig
85
swiftmailer .
84 :
provides email sending features based on
86 : provides a template engine based on Twig87 .
https://github.com/yiisoft/yii2-faker
https://github.com/fzaninotto/Faker
71
https://github.com/yiisoft/yii2-gii
72
https://github.com/yiisoft/yii2-imagine
73
http://imagine.readthedocs.org/
74
https://github.com/yiisoft/yii2-jui
75
http://jqueryui.com/
76
https://github.com/yiisoft/yii2-mongodb
77
http://www.mongodb.org/
78
https://github.com/yiisoft/yii2-redis
79
http://redis.io/
80
https://github.com/yiisoft/yii2-smarty
81
http://www.smarty.net/
82
https://github.com/yiisoft/yii2-sphinx
83
http://sphinxsearch.com
84
https://github.com/yiisoft/yii2-swiftmailer
85
http://swiftmailer.org/
86
https://github.com/yiisoft/yii2-twig
87
http://twig.sensiolabs.org/
70
142
CHAPTER 3.
APPLICATION STRUCTURE
Chapter 4
Handling Requests
4.1 Overview
Each time when a Yii application handles a request, it undergoes a similar
workow.
1. A user makes a request to the entry script
web/index.php.
2. The entry script loads the application conguration and creates an
application instance to handle the request.
3. The application resolves the requested route with the help of the request application component.
4. The application creates a controller instance to handle the request.
5. The controller creates an action instance and performs the lters for
the action.
6. If any lter fails, the action is cancelled.
7. If all lters 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 dierent libraries are registered.
This includes the Composer autoloader through its
Yii autoloader through its
Yii
class le.
autoload.php
le and the
The entry script then loads the
application conguration and creates an application instance.
In the constructor of the application, the following bootstrapping work
is done:
1.
preInit()
is called, which congures some high priority application
properties, such as
2. Register the
basePath.
error handler.
3. Initialize application properties using the given application conguration.
4.
init() is called which in turn calls bootstrap() to run bootstrapping
components.
4.3.
ROUTING AND URL CREATION
•
•
•
145
Include the extension manifest le
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 bootstrapping 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 eect before they are used to resolve requests.
In production mode, enable a bytecode cache, such as PHP OPcache
2
1 or
APC , to minimize the time needed for including and parsing PHP les.
Some large applications have very complex application congurations
which are divided into many smaller conguration les. If this is the case,
consider caching the whole conguration array and loading it directly from
cache before creating the application instance in the entry script.
4.3 Routing and URL Creation
When a Yii application starts processing a requested URL, the rst step it
takes is to parse the URL into a route. The route is then used to instantiate
the corresponding controller action to handle the request. This whole process
is called
routing.
The reverse process of routing is called
URL creation,
which creates a
URL from a given route and the associated query parameters.
When the
created URL is later requested, the routing process can resolve it back into
the original route and query parameters.
The central piece responsible for routing and URL creation is the
URL
manager, which is registered as the urlManager application component. The
URL manager provides the parseRequest() method to parse an incoming request into a route and the associated query parameters and the createUrl()
method to create a URL from a given route and its associated query parameters.
By conguring the
urlManager
component in the application congura-
tion, you can let your application recognize arbitrary URL formats without
modifying your existing application code.
following code to create a URL for the
For example, you can use the
post/view
use yii\helpers\Url;
1
2
http://php.net/manual/en/intro.opcache.php
http://php.net/manual/en/book.apc.php
action:
146
CHAPTER 4.
HANDLING REQUESTS
// Url::to() calls UrlManager::createUrl() to create a URL
$url = Url::to(['post/view', 'id' => 100]);
urlManager
Depending on the
conguration, the created URL may look like
one of the following (or other format). And if the created URL is requested
later, it will still be parsed back into the original route and query parameter
value.
/index.php?r=post/view&id=100
/index.php/post/100
/posts/100
4.3.1 URL Formats
The
URL manager
supports two URL formats: the default URL format and
the pretty URL format.
The default URL format uses a query parameter named
r
to represent
the route and normal query parameters to represent the query parameters
associated with the route. For example, the URL
=100
represents the route
post/view
and the
id
/index.php?r=post/view&id
query parameter 100.
default URL format does not require any conguration of the
The
URL manager
and works in any Web server setup.
The pretty URL format uses the extra path following the entry script
name to represent the route and the associated query parameters. For example, the extra path in the URL
represent the route
post/view
and
/index.php/post/100 is /post/100 which may
the id query parameter 100 with a proper
URL rule. To use the pretty URL format, you will need to design a set of
URL rules according to the actual requirement about how the URLs should
look like.
You may switch between the two URL formats by toggling the
property of the
URL manager
enablePrettyUrl
without changing any other application code.
4.3.2 Routing
Routing involves two steps. In the rst step, the incoming request is parsed
into a route and the associated query parameters.
In the second step, a
controller action corresponding to the parsed route is created to handle the
request.
When using the default URL format, parsing a request into a route is as
simple as getting the value of a
GET
query parameter named
When using the pretty URL format, the
registered
URL rules
URL manager
r.
will examine the
to nd matching one that can resolve the request into
a route. If such a rule cannot be found, a
exception will be thrown.
yii\web\NotFoundHttpException
4.3.
ROUTING AND URL CREATION
147
Once the request is parsed into a route, it is time to create the controller
action identied by the route. The route is broken down into multiple parts
by the slashes in it.
index.
For example,
site/index
will be broken into
site
and
Each part is an ID which may refer to a module, a controller or an
action. Starting from the rst part in the route, the application takes the
following steps to create modules (if any), controller and action:
1. Set the application as the current module.
2. Check if the
controller map
of the current module contains the cur-
rent ID. If so, a controller object will be created according to the
controller conguration found in the map, and Step 5 will be taken to
handle the rest part of the route.
3. Check if the ID refers to a module listed in the
of the current module.
modules
property
If so, a module is created according to the
conguration found in the module list, and Step 2 will be taken to
handle the next part of 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
action map.
If found,
it creates an action according to the conguration found in the map.
Otherwise, the controller will attempt to create an inline action which
is dened by an action method corresponding to the current ID.
Among the above steps, if any error occurs, a
yii\web\NotFoundHttpException
will be thrown, indicating the failure of the routing process.
Default Route
When a request is parsed into an empty route, the so-called
will be used, instead.
refers to the
index
conguring the
By default, the default route is
action of the
site
];
which
You may customize it by
defaultRoute property of the application in the application
conguration like the following:
[
controller.
default route
site/index,
// ...
'defaultRoute' => 'main/index',
148
CHAPTER 4.
catchAll
HANDLING REQUESTS
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
congure the
yii\web\Application::$catchAll property like the following
in the application conguration:
[
];
// ...
'catchAll' => ['site/offline'],
With the above conguration, the
site/offline
action will be used to handle
all incoming requests.
The
catchAll property should take an array whose rst element species a
route, and the rest of the elements (name-value pairs) specify the parameters
to be bound to the action.
4.3.3 Creating URLs
Yii provides a helper method
yii\helpers\Url::to()
to create various
kinds of URLs from given routes and their associated query parameters.
For example,
use yii\helpers\Url;
// creates a URL to a route: /index.php?r=post/index
echo Url::to(['post/index']);
// creates a URL to a route with parameters: /index.php?r=post/view&id=100
echo Url::to(['post/view', 'id' => 100]);
// creates an anchored URL: /index.php?r=post/view&id=100#content
echo Url::to(['post/view', 'id' => 100, '#' => 'content']);
// creates an absolute URL: http://www.example.com/index.php?r=post/index
echo Url::to(['post/index'], true);
// creates an absolute URL using the https scheme: https://www.example.com/
index.php?r=post/index
echo Url::to(['post/index'], 'https');
Note that in the above example, we assume the default URL format is being
used. If the pretty URL format is enabled, the created URLs will be dierent,
URL rules in use.
The route passed to the yii\helpers\Url::to() method is context sen-
according to the
sitive. It can be either a
relative
route or an
absolute
route which will be
normalized according to the following rules:
•
If the route is an empty string, the currently requested
used;
route
will be
4.3.
ROUTING AND URL CREATION
•
149
If the route contains no slashes at all, it is considered to be an action
ID of the current controller and will be prepended with the
uniqueId
value of the current controller;
•
If the route has no leading slash, it is considered to be a route relative
to the current module and will be prepended with the
uniqueId
value
of the current module.
Starting from version 2.0.2, you may specify a route in terms of an alias. If
this is the case, the alias will rst be converted into the actual route which
will then be turned into an absolute route according to the above rules.
For example, assume the current module is
troller is
admin
and the current con-
post,
use yii\helpers\Url;
// currently requested route: /index.php?r=admin/post/index
echo Url::to(['']);
// a relative route with action ID only: /index.php?r=admin/post/index
echo Url::to(['index']);
// a relative route: /index.php?r=admin/post/index
echo Url::to(['post/index']);
// an absolute route: /index.php?r=post/index
echo Url::to(['/post/index']);
// /index.php?r=post/index
post/index"
echo Url::to(['@posts']);
assume the alias "@posts" is defined as "/
yii\helpers\Url::to() method is implemented by calling the createUrl()
createAbsoluteUrl() methods of the URL manager. In the next few
subsections, we will explain how to congure the URL manager to customize
The
and
the format of the created URLs.
The
yii\helpers\Url::to()
method also supports creating URLs that
are NOT related with particular routes. Instead of passing an array as its
rst parameter, you should pass a string in this case. For example,
use yii\helpers\Url;
// currently requested URL: /index.php?r=admin/post/index
echo Url::to();
// an aliased URL: http://example.com
Yii::setAlias('@example', 'http://example.com/');
echo Url::to('@example');
// an absolute URL: http://example.com/images/logo.gif
echo Url::to('/images/logo.gif', true);
Besides the
to()
method, the
yii\helpers\Url
helper class also provides
several other convenient URL creation methods. For example,
150
CHAPTER 4.
HANDLING REQUESTS
use yii\helpers\Url;
// home page URL: /index.php?r=site/index
echo Url::home();
// the base URL, useful if the application is deployed in a sub-folder of
the Web root
echo Url::base();
// the canonical URL of the currently requested URL
// see https://en.wikipedia.org/wiki/Canonical_link_element
echo Url::canonical();
// remember the currently requested URL and retrieve it back in later
requests
Url::remember();
echo Url::previous();
4.3.4 Using Pretty URLs
To use pretty URLs, congure the
urlManager
component in the application
conguration like the following:
[
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'enableStrictParsing' => false,
'rules' => [
// ...
],
],
],
]
The
enablePrettyUrl
property is mandatory as it toggles the pretty URL
format. The rest of the properties are optional. However, their conguration
shown above is most commonly used.
• showScriptName:
this property determines whether the entry script
should be included in the created URLs. For example, instead of cre-
/index.php/post/100, by
/post/100 will be generated.
ating a URL
a URL
• enableStrictParsing:
strict request parsing.
setting this property to be false,
this property determines whether to enable
If strict parsing is enabled, the incoming re-
rules in order to be
yii\web\NotFoundHttpException will
is disabled, when none of the rules
quested URL must match at least one of the
treated as a valid request, or a
be thrown.
If strict parsing
matches the requested URL, the path info part of the URL will be
treated as the requested route.
4.3.
ROUTING AND URL CREATION
• rules:
151
this property contains a list of rules specifying how to parse
and create URLs. It is the main property that you should work with in
order to create URLs whose format satises your particular application
requirement.
Note: In order to hide the entry script name in the created URLs,
besides setting
showScriptName to be false, you may also need to
congure your Web server so that it can correctly identify which
PHP script should be executed when a requested URL does not
explicitly specify one. If you are using Apache Web server, you
may refer to the recommended conguration as described in the
Installation section.
URL Rules
A URL rule is an instance of
yii\web\UrlRule or its child class.
Each URL
rule consists of a pattern used for matching the path info part of URLs, a
route, and a few query parameters.
A URL rule can be used to parse a
request if its pattern matches the requested URL. A URL rule can be used
to create a URL if its route and query parameter names match those that
are given.
When the pretty URL format is enabled, the
rules declared in its
rules
URL manager uses the URL
property to parse incoming requests and create
URLs. In particular, to parse an incoming request, the
URL manager
ines the rules in the order they are declared and looks for the
rst
exam-
rule that
matches the requested URL. The matching rule is then used to parse the
URL into a route and its associated parameters. Similarly, to create a URL,
the
URL manager
looks for the rst rule that matches the given route and
parameters and uses that to create a URL.
You can congure
yii\web\UrlManager::$rules
as an array with keys
being the patterns and values the corresponding routes. Each pattern-route
pair constructs a URL rule. For example, the following
declares two URL rules. The rst rule matches a URL
post/index.
expression post/(\d+)
named id.
the route
[
]
rules
posts
conguration
and maps it into
The second rule matches a URL matching the regular
and maps it into the route
post/view
and a parameter
'posts' => 'post/index',
'post/<id:\d+>' => 'post/view',
Info: The pattern in a rule is used to match the path info part of
a URL. For example, the path info of
ad
is
post/100
/index.php/post/100?source=
(the leading and ending slashes are ignored) which
matches the pattern
post/(\d+).
152
CHAPTER 4.
HANDLING REQUESTS
Besides declaring URL rules as pattern-route pairs, you may also declare
them as conguration arrays. Each conguration array is used to congure
a single URL rule object. This is often needed when you want to congure
other properties of a URL rule. For example,
[
// ...other url rules...
[
]
],
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '.json',
By default if you do not specify the
will take the default class
class
option for a rule conguration, it
yii\web\UrlRule.
Named Parameters
A URL rule can be associated with a few named query parameters which are
<ParamName:RegExp>, where ParamName
RegExp is an optional regular expression
If RegExp is not specied, it means the
specied in the pattern in the format of
species the parameter name and
used to match parameter values.
parameter value should be a string without any slash.
Note: You can only specify regular expressions for parameters.
The rest part of a pattern is considered as plain text.
When a rule is used to parse a URL, it will ll the associated parameters with
values matching the corresponding parts of the URL, and these parameters
will be made available in
$_GET
later by the
request
application component.
When the rule is used to create a URL, it will take the values of the provided
parameters and insert them at the places where the parameters are declared.
Let's use some examples to illustrate how named parameters work. Assume we have declared the following three URL rules:
[
]
'posts/<year:\d{4}>/<category>' => 'post/index',
'posts' => 'post/index',
'post/<id:\d+>' => 'post/view',
When the rules are used to parse URLs:
• /index.php/posts
is parsed into the route
post/index
using the second
rule;
• /index.php/posts/2014/php
is parsed into the route
parameter whose value is 2014 and the
is
php
using the rst rule;
category
post/index,
the
year
parameter whose value
4.3.
ROUTING AND URL CREATION
• /index.php/post/100
is parsed into the route
153
post/view
and the
id
pa-
rameter whose value is 100 using the third rule;
• /index.php/posts/php will cause a yii\web\NotFoundHttpException when
yii\web\UrlManager::$enableStrictParsing is true, because it matches
none of the patterns. If yii\web\UrlManager::$enableStrictParsing
is
false (the default value), the path info part posts/php will be returned
as the route.
And when the rules are used to create URLs:
• Url::to(['post/index']) creates /index.php/posts using the second rule;
• Url::to(['post/index', 'year' => 2014, 'category' => 'php']) creates /
index.php/posts/2014/php
using the rst rule;
• Url::to(['post/view', 'id' => 100]) creates /index.php/post/100 using the
third rule;
• Url::to(['post/view', 'id' => 100, 'source' => 'ad']) creates /index.php
/post/100?source=ad
using the third rule. Because the
source
parameter
is not specied in the rule, it is appended as a query parameter in the
created URL.
• Url::to(['post/index', 'category' => 'php']) creates /index.php/post/index
?category=php
using none of the rules. Note that since none of the rules
applies, the URL is created by simply appending the route as the path
info and all parameters as the query string part.
Parameterizing Routes
You can embed parameter names in the route of a URL rule. This allows a
URL rule to be used for matching multiple routes. For example, the following
rules embed
[
]
controller
and
action
parameters in the routes.
'<controller:(post|comment)>/<id:\d+>/<action:(create|update|delete)>'
=> '<controller>/<action>',
'<controller:(post|comment)>/<id:\d+>' => '<controller>/view',
'<controller:(post|comment)>s' => '<controller>/index',
/index.php/comment/100/create, the rst rule will apply, which
sets the controller parameter to be comment and action parameter to be create.
The route <controller>/<action> is thus resolved as comment/create.
Similarly, to create a URL for the route comment/index, the third rule will
apply, which creates a URL /index.php/comments.
To parse a URL
Info: By parameterizing routes, it is possible to greatly reduce
the number of URL rules, which can signicantly improve the
performance of
URL manager.
By default, all parameters declared in a rule are required.
If a requested
URL does not contain a particular parameter, or if a URL is being created
154
CHAPTER 4.
HANDLING REQUESTS
without a particular parameter, the rule will not apply. To make some of
the parameters optional, you can congure the
defaults
property of a rule.
Parameters listed in this property are optional and will take the specied
values when they are not provided.
In the following rule declaration, the
page
and
tag
parameters are both
optional and will take the value of 1 and empty string, respectively, when
they are not provided.
[
]
// ...other rules...
[
'pattern' => 'posts/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => ''],
],
The above rule can be used to parse or create any of the following URLs:
•
•
•
•
/index.php/posts: page is 1, tag is `'.
/index.php/posts/2: page is 2, tag is `'.
/index.php/posts/2/news: page is 2, tag is 'news'.
/index.php/posts/news: page is 1, tag is 'news'.
Without using optional parameters, you would have to create 4 rules to
achieve the same result.
Rules with Server Names
It is possible to include Web server names in the patterns of URL rules.
This is mainly useful when your application should behave dierently for
dierent Web server names. For example, the following rules will parse the
URL http://admin.example.com/login into
://www.example.com/login into site/login.
[
]
the route
admin/user/login
and
http
'http://admin.example.com/login' => 'admin/user/login',
'http://www.example.com/login' => 'site/login',
You can also embed parameters in the server names to extract dynamic infor-
http
language
mation from them. For example, the following rule will parse the URL
://en.example.com/posts
=en.
[
]
into the route
post/index
and the parameter
'http://<language:\w+>.example.com/posts' => 'post/index',
Note: Rules with server names should NOT include the subfolder
of the entry script in their patterns.
plication is under
For example, if the ap-
http://www.example.com/sandbox/blog,
then you
4.3.
ROUTING AND URL CREATION
155
http://www.example.com/posts
http://www.example.com/sandbox/blog/posts. This will
should use the pattern
instead of
allow your
application to be deployed under any directory without the need
to change your application code.
URL Suxes
You may want to add suxes to the URLs for various purposes. For example,
you may add
.html
to the URLs so that they look like URLs for static HTML
pages; you may also add
type of the response.
.json
to the URLs to indicate the expected content
You can achieve this goal by conguring the
\web\UrlManager::$suffix
yii
property like the following in the application
conguration:
[
]
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'enableStrictParsing' => true,
'suffix' => '.html',
'rules' => [
// ...
],
],
],
The above conguration will allow the
URLs and also create URLs with
Tip: You may set
/
.html
URL manager
to recognize requested
as their sux.
as the URL sux so that the URLs all end
with a slash.
Note:
When you congure a URL sux, if a requested URL
does not have the sux, it will be considered as an unrecognized
URL. This is a recommended practice for SEO (search engine
optimization).
Sometimes you may want to use dierent suxes for dierent URLs. This
can be achieved by conguring the
suffix property of individual URL rules.
When a URL rule has this property set, it will override the sux setting at
the
URL manager
level. For example, the following conguration contains a
customized URL rule which uses
.json
.html.
[
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
as its sux instead of the global one
156
]
CHAPTER 4.
],
],
HANDLING REQUESTS
'showScriptName' => false,
'enableStrictParsing' => true,
'suffix' => '.html',
'rules' => [
// ...
[
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '.json',
],
],
HTTP Methods
When implementing RESTful APIs, it is commonly needed that the same
URL be parsed into dierent routes according to the HTTP methods being
used. This can be easily achieved by prexing the supported HTTP methods
to the patterns of the rules.
If a rule supports multiple HTTP methods,
separate the method names with commas. For example, the following rules
post/<id:\d+> with dierent HTTP method support.
A request for PUT post/100 will be parsed into post/create, while a request for
GET post/100 will be parsed into post/view.
have the same pattern
[
]
'PUT,POST post/<id:\d+>' => 'post/create',
'DELETE post/<id:\d+>' => 'post/delete',
'post/<id:\d+>' => 'post/view',
Note: If a URL rule contains HTTP method(s) in its pattern,
the rule will only be used for parsing purpose. It will be skipped
when the
URL manager
is called to create URLs.
Tip: To simplify the routing of RESTful APIs, Yii provides a
special URL rule class
yii\rest\UrlRule
which is very ecient
and supports some fancy features such as automatic pluralization
of controller IDs. For more details, please refer to the Routing
section about developing RESTful APIs.
Customizing Rules
In the previous examples, URL rules are mainly declared in terms of patternroute pairs. This is a commonly used shortcut format. In certain scenarios,
you may want to customize a URL rule by conguring its other properties,
4.3.
ROUTING AND URL CREATION
such as
yii\web\UrlRule::$suffix.
157
This can be done by using a full con-
guration array to specify a rule. The following example is extracted from
the URL Suxes subsection,
[
// ...other url rules...
[
],
]
'pattern' => 'posts',
'route' => 'post/index',
'suffix' => '.json',
Info: By default if you do not specify the
class
conguration, it will take the default class
option for a rule
yii\web\UrlRule.
Adding Rules Dynamically
URL rules can be dynamically added to the
URL manager.
This is often
needed by redistributable modules which want to manage their own URL
rules.
In order for the dynamically added rules to take eect during the
routing process, you should add them during the bootstrapping stage. For
modules, this means they should implement
and add the rules in the
bootstrap()
yii\base\BootstrapInterface
method like the following:
public function bootstrap($app)
{
$app->getUrlManager()->addRules([
// rule declarations here
], false);
}
Note that you should also list these modules in
bootstrap()
yii\web\Application::
so that they can participate the bootstrapping process.
Creating Rule Classes
Despite the fact that the default
yii\web\UrlRule
class is exible enough
for the majority of projects, there are situations when you have to create
your own rule classes. For example, in a car dealer Web site, you may want
to support the URL format like
and
Model
/Manufacturer/Model,
where both
must match some data stored in a database table.
Manufacturer
The default
rule class will not work here because it relies on statically declared patterns.
We can create the following URL rule class to solve this problem.
namespace app\components;
use yii\web\UrlRuleInterface;
use yii\base\Object;
158
CHAPTER 4.
HANDLING REQUESTS
class CarUrlRule extends Object implements UrlRuleInterface
{
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
}
And use the new rule class in the
yii\web\UrlManager::$rules
congura-
tion:
[
// ...other rules...
[
]
],
'class' => 'app\components\CarUrlRule',
// ...configure other properties...
4.3.5 Performance Consideration
When developing a complex Web application, it is important to optimize
URL rules so that it takes less time to parse requests and create URLs.
By using parameterized routes, you may reduce the number of URL rules,
which can signicantly improve performance.
When parsing or creating URLs,
URL manager examines URL rules in the
order they are declared. Therefore, you may consider adjusting the order of
the URL rules so that more specic and/or more commonly used rules are
placed before less used ones.
4.4.
REQUESTS
159
If some URL rules share the same prex in their patterns or routes,
yii\web\GroupUrlRule so that they can be more
URL manager as a group. This is often the case when
you may consider using
eciently examined by
your application is composed by modules, each having its own set of URL
rules with module ID as their common prexes.
4.4 Requests
Requests made to an application are represented in terms of
yii\web\Request
objects which provide information such as request parameters, HTTP headers, cookies, etc. For a given request, you can get access to the corresponding
request object via the
yii\web\Request,
request
application component which is an instance of
by default. 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
request
get()
component. They return the values of
and
$_GET
post()
and
methods of the
$_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;
$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 as 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
160
CHAPTER 4.
these parameters by calling the
HANDLING REQUESTS
yii\web\Request::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
PATCH
GET
parameters, parameters submitted via
etc. are sent in the request body. The
request
POST, PUT,
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 conguring the
$parsers
yii\web\Request::
property.
4.4.2 Request Methods
You can get the HTTP method used by the current request via the expression
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
if
if
if
($request->isAjax)
($request->isGet)
($request->isPost)
($request->isPut)
{
{
{
{
//
//
//
//
the
the
the
the
request
request
request
request
is an AJAX request }
method is GET }
method is POST }
method is PUT }
4.4.3 Request URLs
The
request
component provides many ways of inspecting the currently re-
quested URL.
Assuming the URL being requested is
/product?id=100,
http://example.com/admin/index.php
you can get various parts of this URL as summarized in the
following:
• url:
returns
/admin/index.php/product?id=100, which is the URL without
the host info part.
• absoluteUrl:
returns
http://example.com/admin/index.php/product?id=100
, which is the whole URL including the host info part.
• hostInfo:
returns
http://example.com,
returns
/product,
which is the host info part of the
URL.
• pathInfo:
which is the part after the entry script and
before the question mark (query string).
4.4.
REQUESTS
161
• queryString: returns id=100, which is the part after the question mark.
• baseUrl: returns /admin, which is the part after the host info and before
the entry script name.
• scriptUrl:
returns
/admin/index.php,
which is the URL without path
info and query string.
• serverName:
• serverPort:
returns
example.com,
which is the host name in the URL.
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
returned by the
yii\web\Request::$headers
header collection
property. For example,
// $headers is an object of yii\web\HeaderCollection
$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:
• userAgent: returns the value of the User-Agent header.
• contentType: returns the value of the Content-Type header
which indi-
cates the MIME type of the data in the request body.
• acceptableContentTypes:
returns the content MIME types accept-
able by users. The returned types are ordered by their quality score.
Types with the highest scores will be returned rst.
• acceptableLanguages:
returns the languages acceptable by users. The
returned languages are ordered by their preference level. The rst 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
yii\web\Request::getPreferredLanguage().
This method takes a list of languages supported by your application, compares them with
acceptableLanguages,
and returns the most appropriate
language.
Tip: You may also use the
ContentNegotiator
lter to dynam-
ically determine what content type and language should be used
in the response.
The lter implements the content negotiation
on top of 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
userHost
and
userIP,
respectively. For example,
162
CHAPTER 4.
HANDLING REQUESTS
$userHost = Yii::$app->request->userHost;
$userIP = Yii::$app->request->userIP;
4.5 Responses
When an application nishes handling a request, it generates a
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
ponent which is an instance of
response
application com-
yii\web\Response, by default.
However, Yii
also allows you to create your own response objects and send them to end
users as we will explain in the following.
In this section, we will describe how to compose and send responses to
end users.
4.5.1 Status Code
One of the rst things you would do when building a response is to state
whether the request is successfully handled.
yii\web\Response::$statusCode
3
HTTP status codes .
This is done by setting the
property which can take one of the valid
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
yii\web\Response::$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 \yii\web\NotFoundHttpException;
When the error handler catches an exception, it will extract the status
For the yii\web
\NotFoundHttpException above, it is associated with the HTTP status 404.
code from the exception and assign it to the response.
The following HTTP exceptions are predened in Yii:
•
•
•
•
•
•
•
3
yii\web\BadRequestHttpException: status code 400.
yii\web\ConflictHttpException: status code 409.
yii\web\ForbiddenHttpException: status code 403.
yii\web\GoneHttpException: status code 410.
yii\web\MethodNotAllowedHttpException: status code 405.
yii\web\NotAcceptableHttpException: status code 406.
yii\web\NotFoundHttpException: status code 404.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
4.5.
RESPONSES
•
•
•
•
163
yii\web\ServerErrorHttpException: status code 500.
yii\web\TooManyRequestsHttpException: status code 429.
yii\web\UnauthorizedHttpException: status code 401.
yii\web\UnsupportedMediaTypeHttpException: status code
415.
If the exception that you want to throw is not among the above list, you may
create one by extending from
yii\web\HttpException,
or directly throw it
with a status code, for example,
throw new \yii\web\HttpException(402);
4.5.2 HTTP Headers
You can send HTTP headers by manipulating the
the
response
header collection
in
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->set('Pragma', 'no-cache');
// remove Pragma header(s) and return the removed Pragma header values in an
array
$values = $headers->remove('Pragma');
Info: Header names are case insensitive.
And the newly regis-
tered headers are not sent to the user until the
::send()
yii\web\Response
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
yii\web\Response::$content
property of the response. For example,
Yii::$app->response->content = 'hello world!';
If your data needs to be formatted before sending it to end users, you should
set both of the
format and data properties. The format property
data should be formatted. For example,
species
in which format the
$response = Yii::$app->response;
$response->format = \yii\web\Response::FORMAT_JSON;
$response->data = ['message' => 'hello world'];
Yii supports the following formats out of the box, each implemented by a
formatter
class. You can customize these formatters or add new ones by
conguring the
yii\web\Response::$formatters
property.
164
CHAPTER 4.
•
•
•
•
•
HANDLING REQUESTS
HTML: implemented by yii\web\HtmlResponseFormatter.
XML: implemented by yii\web\XmlResponseFormatter.
JSON: implemented by yii\web\JsonResponseFormatter.
JSONP: implemented by yii\web\JsonResponseFormatter.
RAW: use this format if you want to send the response directly
without
applying any formatting.
While the 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
return value will be taken by the
response
index
view. The
component, formatted and then
sent to end users.
Because by default the response format is
HTML,
you should only return
a string in an action method. If you want to use a dierent response format,
you should set it rst before returning the data. For example,
public function actionInfo()
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return [
'message' => 'hello world',
'code' => 100,
];
}
As aforementioned, besides using the default
response
application compo-
nent, you can also create your own response objects and send them to end
users. You can do so by returning such object in an action method, like the
following,
public function actionInfo()
{
return \Yii::createObject([
'class' => 'yii\web\Response',
'format' => \yii\web\Response::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 congurations that you
set for the
response
component in the application conguration.
4.5.
RESPONSES
165
You can, however, use dependency injection to apply a common
conguration to your new response objects.
4.5.4 Browser Redirection
Browser redirection relies on sending a
Location
HTTP header. Because this
feature is commonly used, Yii provides some special support for it.
You can redirect the user browser to a URL by calling the
\Response::redirect() method.
The method sets the appropriate
yii\web
Location
header with the given URL and returns the response object itself.
an action method, you can call its shortcut version
::redirect().
In
yii\web\Controller
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
method.
redirect()
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 yii\web\Response
::redirect() directly followed by a chained call to the yii\web\Response
::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
yii\web\Response::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 to redirect. To solve this problem, the
yii\web\Response::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 le which provides a set
of commonly used JavaScript utilities, including browser redirection based on the
X-Redirect
header. Therefore, if you are using
this JavaScript le (by registering the
yii\web\YiiAsset
asset
bundle), you do not need to write anything to support AJAX
redirection.
166
CHAPTER 4.
HANDLING REQUESTS
4.5.5 Sending Files
Like browser redirection, le sending is another feature that relies on specic
HTTP headers. Yii provides a set of methods to support various le sending
needs. They all have built-in support for the HTTP range header.
• yii\web\Response::sendFile(): sends an existing le to
• yii\web\Response::sendContentAsFile(): sends a text
a client.
string as a
le to a client.
• yii\web\Response::sendStreamAsFile():
sends an existing le stream
as a le to a client.
These methods have the same method signature with the response object
as the return value.
using
If the le to be sent is very big, you should consider
yii\web\Response::sendStreamAsFile() because it is more memory
ecient.
The following example shows how to send a le in a controller
action:
public function actionDownload()
{
return \Yii::$app->response->sendFile('path/to/file.txt');
}
If you are calling the le sending method in places other than an action
method, you should also call the
yii\web\Response::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 le sending support called
X-Sendle.
The
idea is to redirect the request for a le to the Web server which will directly
serve the le. As a result, the Web application can terminate earlier while
the Web server is sending the le.
To use this feature, you may call the
yii\web\Response::xSendFile().
The following list summarizes how to
enable the
•
•
•
•
•
X-Sendfile
feature for some popular Web servers:
4
Apache: X-Sendle
5
Lighttpd v1.4: X-LIGHTTPD-send-le
6
Lighttpd v1.5: X-Sendle
7
Nginx: X-Accel-Redirect
Cherokee: X-Sendle and X-Accel-Redirect
8
4.5.6 Sending Response
The content in a response is not sent to the user until the
::send()
4
yii\web\Response
method is called. By default, this method will be called automat-
http://tn123.org/mod_xsendfile
http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file
6
http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file
7
http://wiki.nginx.org/XSendfile
8
http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile
5
4.6.
SESSIONS AND COOKIES
ically at the end of
167
yii\base\Application::run().
You can, however,
explicitly call this method to force sending out the response immediately.
The
yii\web\Response::send()
method takes the following steps to
send out a response:
1. Trigger the
yii\web\Response::EVENT_BEFORE_SEND
yii\web\Response::prepare()
response content.
2. Call
3. Trigger the
4. Call
to format
event.
response data
yii\web\Response::EVENT_AFTER_PREPARE
yii\web\Response::sendHeaders()
into
event.
to send out the registered
HTTP headers.
5. Call
yii\web\Response::sendContent() to send out the response body
content.
6. Trigger the
After the
yii\web\Response::EVENT_AFTER_SEND
yii\web\Response::send()
event.
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
useful events.
yii\web\Response::send() method triggers several
By responding to these events, it is possible to adjust or
decorate the response.
4.6 Sessions and Cookies
Sessions and cookies allow data to be persisted across multiple user requests.
In plain PHP you may access them through the global variables
$_COOKIE,
$_SESSION and
respectively. Yii encapsulates sessions and cookies as objects and
thus allows you to access them in an object-oriented fashion with additional
useful enhancements.
4.6.1 Sessions
Like requests and responses, you can get access to sessions via the
application component which is an instance of
Opening and Closing Sessions
To open and close a session, you can do the following:
$session = Yii::$app->session;
// check if a session is already open
session
yii\web\Session, by default.
168
CHAPTER 4.
HANDLING REQUESTS
if ($session->isActive) ...
// open a session
$session->open();
// close a session
$session->close();
// destroys all data registered to a session.
$session->destroy();
You can call
open()
and
close()
multiple times without causing errors;
internally the methods will rst check if the session is already open.
Accessing Session Data
To access the data stored in session, you can do the following:
$session = Yii::$app->session;
// get a session variable. The following usages are equivalent:
$language = $session->get('language');
$language = $session['language'];
$language = isset($_SESSION['language']) ? $_SESSION['language'] : null;
// set a session variable. The following usages are equivalent:
$session->set('language', 'en-US');
$session['language'] = 'en-US';
$_SESSION['language'] = 'en-US';
// remove a session variable. The following usages are equivalent:
$session->remove('language');
unset($session['language']);
unset($_SESSION['language']);
//
if
if
if
check if a session variable exists. The following usages are equivalent:
($session->has('language')) ...
(isset($session['language'])) ...
(isset($_SESSION['language'])) ...
// traverse all session variables. The following usages are equivalent:
foreach ($session as $name => $value) ...
foreach ($_SESSION as $name => $value) ...
Info: When you access session data through the
session
compo-
nent, a session will be automatically opened if it has not been
done so before.
through
This is dierent from accessing session data
$_SESSION,
which requires an explicit call of
session_start
().
When working with session data that are arrays, the
session
component has
a limitation which prevents you from directly modifying an array element.
For example,
4.6.
SESSIONS AND COOKIES
169
$session = Yii::$app->session;
// the following code will NOT work
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;
// the following code works:
$session['captcha'] = [
'number' => 5,
'lifetime' => 3600,
];
// the following code also works:
echo $session['captcha']['lifetime'];
You can use one of the following workarounds to solve this problem:
$session = Yii::$app->session;
// directly use $_SESSION (make sure Yii::$app->session->open() has been
called)
$_SESSION['captcha']['number'] = 5;
$_SESSION['captcha']['lifetime'] = 3600;
// get the whole array first, modify it and then save it back
$captcha = $session['captcha'];
$captcha['number'] = 5;
$captcha['lifetime'] = 3600;
$session['captcha'] = $captcha;
// use ArrayObject instead of array
$session['captcha'] = new \ArrayObject;
...
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;
// store array data by keys with a common prefix
$session['captcha.number'] = 5;
$session['captcha.lifetime'] = 3600;
For better performance and code readability, we recommend the last workaround.
That is, instead of storing an array as a single session variable, you store each
array element as a session variable which shares the same key prex with
other array elements.
Custom Session Storage
The default
yii\web\Session class stores session data as les on the server.
Yii also provides the following session classes implementing dierent session
storage:
• yii\web\DbSession: stores session data in a database table.
• yii\web\CacheSession: stores session data in a cache with
of a congured cache component.
the help
170
CHAPTER 4.
HANDLING REQUESTS
9 as the storage
• yii\redis\Session:
stores session data using redis
medium.
• yii\mongodb\Session:
10 .
stores session data in a MongoDB
All these session classes support the same set of API methods. As a result,
you can switch to a dierent session storage class without the need to modify
your application code that uses sessions.
Note: If you want to access session data via
$_SESSION
while using
custom session storage, you must make sure that the session has
already been started by
yii\web\Session::open().
This is be-
cause custom session storage handlers are registered within this
method.
To learn how to congure and use these component classes, please refer to
their API documentation.
Below is an example showing how to congure
yii\web\DbSession in the application conguration to use a database table
for session storage:
return [
'components' => [
'session' => [
'class' => 'yii\web\DbSession',
// 'db' => 'mydb', // the application component ID of the DB
connection. Defaults to 'db'.
// 'sessionTable' => 'my_session', // session table name.
Defaults to 'session'.
],
],
];
You also need to create the following database table to store session data:
CREATE TABLE session
(
id CHAR(40) NOT NULL PRIMARY KEY,
expire INTEGER,
data BLOB
)
where `BLOB' refers to the BLOB-type of your preferred DBMS. Below are
the BLOB types that can be used for some popular DBMS:
•
•
•
MySQL: LONGBLOB
PostgreSQL: BYTEA
MSSQL: BLOB
Note: According to the php.ini setting of
id column. For example,
session.hash_function=sha256, you should use a length 64 instead
you may need to adjust the length of the
if
of 40.
9
10
session.hash_function,
http://redis.io/
http://www.mongodb.org/
4.6.
SESSIONS AND COOKIES
171
Flash Data
Flash data is a special kind of session data which, once set in one request, will
only be available during the next request and will be automatically deleted
afterwards. Flash data is most commonly used to implement messages that
should only be displayed to end users once, such as a conrmation message
displayed after a user successfully submits a form.
You can set and access ash data through the
session
application com-
ponent. For example,
$session = Yii::$app->session;
// Request #1
// set a flash message named as "postDeleted"
$session->setFlash('postDeleted', 'You have successfully deleted your post.'
);
// Request #2
// display the flash message named "postDeleted"
echo $session->getFlash('postDeleted');
// Request #3
// $result will be false since the flash message was automatically deleted
$result = $session->hasFlash('postDeleted');
Like regular session data, you can store arbitrary data as ash data.
When you call
yii\web\Session::setFlash(), it will overwrite any ex-
isting ash data that has the same name.
To append new ash data to
an existing message of the same name, you may call
addFlash()
yii\web\Session::
instead. For example:
$session = Yii::$app->session;
// Request #1
// add a few flash messages under
$session->addFlash('alerts', 'You
$session->addFlash('alerts', 'You
$session->addFlash('alerts', 'You
the name of "alerts"
have successfully deleted your post.');
have successfully added a new friend.');
are promoted.');
// Request #2
// $alerts is an array of the flash messages under the name of "alerts"
$alerts = $session->getFlash('alerts');
yii\web\Session::setFlash() together
yii\web\Session::addFlash() for ash data of the same
Note: Try not to use
with
name. This is because the latter method will automatically turn
the ash data into an array so that it can append new ash data
of the same name. As a result, when you call
yii\web\Session
::getFlash(), you may nd sometimes you are getting an array
while sometimes you are getting a string, depending on the order
of the invocation of these two methods.
172
CHAPTER 4.
HANDLING REQUESTS
4.6.2 Cookies
Yii represents each cookie as an object of
\Request
and
yii\web\Response
property named
cookies.
yii\web\Cookie.
Both
yii\web
maintain a collection of cookies via the
The cookie collection in the former represents the
cookies submitted in a request, while the cookie collection in the latter represents the cookies that are to be sent to the user.
Reading Cookies
You can get the cookies in the current request using the following code:
// get the cookie collection (yii\web\CookieCollection) from the "request"
component
$cookies = Yii::$app->request->cookies;
// get the "language" cookie value. If the cookie does not exist, return "en
" as the default value.
$language = $cookies->getValue('language', 'en');
// an alternative way of getting the "language" cookie value
if (($cookie = $cookies->get('language')) !== null) {
$language = $cookie->value;
}
// you may also use $cookies like an array
if (isset($cookies['language'])) {
$language = $cookies['language']->value;
}
// check if there is a "language" cookie
if ($cookies->has('language')) ...
if (isset($cookies['language'])) ...
Sending Cookies
You can send cookies to end users using the following code:
// get the cookie collection (yii\web\CookieCollection) from the "response"
component
$cookies = Yii::$app->response->cookies;
// add a new cookie to the response to be sent
$cookies->add(new \yii\web\Cookie([
'name' => 'language',
'value' => 'zh-CN',
]));
// remove a cookie
$cookies->remove('language');
// equivalent to the following
unset($cookies['language']);
4.6.
SESSIONS AND COOKIES
173
Besides the name, value properties shown in the above examples, the yii
\web\Cookie class also denes other properties to fully represent all available cookie information, such as domain, expire. You may congure these
properties as needed to prepare a cookie and then add it to the response's
cookie collection.
Note: For better security, the default value of
::$httpOnly
is set to true.
yii\web\Cookie
This helps mitigate the risk of a
client side script accessing the protected cookie (if the browser
11 for more
supports it). You may read the httpOnly wiki article
details.
Cookie Validation
When you are reading and sending cookies through the
request
and
response
components as shown in the last two subsections, you enjoy the added
security of cookie validation which protects cookies from being modied on
the client side. This is achieved by signing each cookie with a hash string,
which allows the application to tell if a cookie has been modied on the
client side.
If so, the cookie will NOT be accessible through the
collection
of the
request
cookie
component.
Note: Cookie validation only protects cookie values from being
modied. If a cookie fails the validation, you may still access it
through
$_COOKIE.
This is because third-party libraries may ma-
nipulate cookies in their own way, which does not involve cookie
validation.
Cookie validation is enabled by default. You can disable it by setting the
\web\Request::$enableCookieValidation
yii
property to be false, although
we strongly recommend you do not do so.
Note: Cookies that are directly read/sent via
()
$_COOKIE and setcookie
will NOT be validated.
When using cookie validation, you must specify a
yii\web\Request::$cookieValidationKey
that will be used to generate the aforementioned hash strings. You can do
so by conguring the
request
component in the application conguration:
return [
'components' => [
'request' => [
'cookieValidationKey' => 'fill in a secret key here',
],
],
];
11
https://www.owasp.org/index.php/HttpOnly
174
CHAPTER 4.
Info:
cookieValidationKey
HANDLING REQUESTS
is critical to your application's se-
curity. It should only be known to people you trust. Do not store
it in the version control system.
4.7 Handling Errors
Yii includes a built-in
error handler
which makes error handling a much
more pleasant experience than before. In particular, the Yii error handler
does the followings to improve error handling:
•
All non-fatal PHP errors (e.g.
warnings, notices) are converted into
catchable exceptions.
•
Exceptions and fatal PHP errors are displayed with detailed call stack
information and source code lines in debug mode.
•
•
The
Supports using a dedicated controller action to display errors.
Supports dierent error response formats.
error handler
the constant
is enabled by default. You may disable it by dening
YII_ENABLE_ERROR_HANDLER
to be false in the entry script of your
application.
4.7.1 Using Error Handler
The
error handler is registered as an application component named errorHandler
. You may congure it in the application conguration like the following:
return [
'components' => [
'errorHandler' => [
'maxSourceLines' => 20,
],
],
];
With the above conguration, the number of source code lines to be displayed
in exception pages will be up to 20.
As aforementioned, the error handler turns all non-fatal PHP errors into
catchable exceptions.
This means you can use the following code to deal
with PHP errors:
use Yii;
use yii\base\ErrorException;
try {
10/0;
} catch (ErrorException $e) {
Yii::warning("Division by zero.");
}
// execution continues...
4.7.
HANDLING ERRORS
175
If you want to show an error page telling the user that his request is invalid
or unexpected, you may simply throw an
\NotFoundHttpException.
HTTP exception,
such as
yii\web
The error handler will correctly set the HTTP
status code of the response and use an appropriate error view to display the
error message.
use yii\web\NotFoundHttpException;
throw new NotFoundHttpException();
4.7.2 Customizing Error Display
The
error handler
constant
YII_DEBUG.
adjusts the error display according to the value of the
When
YII_DEBUG
is true (meaning in debug mode), the
error handler will display exceptions with detailed call stack information and
source code lines to help easier debugging. And when
YII_DEBUG
is false, only
the error message will be displayed to prevent revealing sensitive information
about the application.
Info: If an exception is a descendant of
yii\base\UserException,
no call stack will be displayed regardless the value of
YII_DEBUG.
This is because such exceptions are considered to be caused by
user mistakes and the developers do not need to x anything.
By default, the
error handler
displays errors using two views:
• @yii/views/errorHandler/error.php:
used when errors should be displayed
WITHOUT call stack information. When
YII_DEBUG
is false, this is the
only error view to be displayed.
• @yii/views/errorHandler/exception.php:
used when errors should be dis-
played WITH call stack information.
You can congure the
errorView and exceptionView properties of the error
handler to use your own views to customize the error display.
Using Error Actions
A better way of customizing the error display is to use dedicated error actions. To do so, rst congure the
errorAction property of the errorHandler
component like the following:
return [
'components' => [
'errorHandler' => [
'errorAction' => 'site/error',
],
]
];
176
CHAPTER 4.
The
errorAction
HANDLING REQUESTS
property takes a route to an action.
The above cong-
uration states that when an error needs to be displayed without call stack
information, the
site/error
action should be executed.
site/error
You can create the
action as follows,
namespace app\controllers;
use Yii;
use yii\web\Controller;
class SiteController extends Controller
{
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
];
}
}
The above code denes the
error
action using the
class which renders an error using a view named
Besides using
yii\web\ErrorAction,
yii\web\ErrorAction
error.
you may also dene the
error
ac-
tion using an action method like the following,
public function actionError()
{
$exception = Yii::$app->errorHandler->exception;
if ($exception !== null) {
return $this->render('error', ['exception' => $exception]);
}
}
You should now create a view le located at
views/site/error.php.
In this
view le, you can access the following variables if the error action is dened
as
yii\web\ErrorAction:
• name: the name of the error;
• message: the error message;
• exception: the exception object
through which you can retrieve more
useful information, such as HTTP status code, error code, error call
stack, etc.
Info: If you are using the basic project template or the advanced
project template
12 , the error action and the error view are al-
ready dened for you.
12
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
4.7.
HANDLING ERRORS
177
Customizing Error Response Format
The error handler displays errors according to the format setting of the response. If the the
response format is html, it will use the error or exception
view to display errors, as described in the last subsection. For other response
formats, the error handler will assign the array representation of the exception to the
yii\web\Response::$data property which will then be converted
to dierent formats accordingly. For example, if the response format is
json,
you may see the following response:
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
You may customize the error response format by responding to the
event of the
response
beforeSend
component in the application conguration:
return [
// ...
'components' => [
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => function ($event) {
$response = $event->sender;
if ($response->data !== null) {
$response->data = [
'success' => $response->isSuccessful,
'data' => $response->data,
];
$response->statusCode = 200;
}
},
],
],
];
The above code will reformat the error response like 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
{
178
}
CHAPTER 4.
HANDLING REQUESTS
"success": false,
"data": {
"name": "Not Found Exception",
"message": "The requested resource was not found.",
"code": 0,
"status": 404
}
4.8 Logging
Yii provides a powerful logging framework that is highly customizable and extensible. Using this framework, you can easily log various types of messages,
lter them, and gather them at dierent targets, such as les, databases,
emails.
Using the Yii logging framework involves the following steps:
•
•
Record log messages at various places in your code;
Congure log targets in the application conguration to lter and export log messages;
•
Examine the ltered logged messages exported by dierent targets (e.g.
the Yii debugger).
In this section, we will mainly describe the rst two steps.
4.8.1 Log Messages
Recording log messages is as simple as calling one of the following logging
methods:
• Yii::trace():
record a message to trace how a piece of code runs.
This is mainly for development use.
• Yii::info(): record a message that conveys some useful information.
• Yii::warning(): record a warning message that indicates something
unexpected has happened.
• Yii::error():
record a fatal error that should be investigated as soon
as possible.
These logging methods record log messages at various
categories.
They share the same function signature
severity levels
and
function ($message,
$category = 'application'), where $message stands for the log message to be
recorded, while $category is the category of the log message. The code in
the following example records a trace message under the default category
application:
Yii::trace('start calculating average revenue');
Info: Log messages can be strings as well as complex data, such
as arrays or objects.
It is the responsibility of log targets to
4.8.
LOGGING
179
properly deal with log messages.
By default, if a log message
is not a string, it will be exported as a string by calling
\helpers\VarDumper::export().
yii
To better organize and lter log messages, it is recommended that you specify
an appropriate category for each log message. You may choose a hierarchical
naming scheme for categories, which will make it easier for log targets to lter
messages based on their categories. A simple yet eective naming scheme is
to use the PHP magic constant
__METHOD__
for the category names. This is
also the approach used in the core Yii framework code. For example,
Yii::trace('start calculating average revenue', __METHOD__);
The
__METHOD__
constant evaluates as the name of the method (prexed with
the fully qualied class name) where the constant appears.
it is equal to the string
For example,
'app\controllers\RevenueController::calculate'
if the
above line of code is called within this method.
Info: The logging methods described above are actually shortcuts to the
log()
method of the
logger object
gleton accessible through the expression
which is a sin-
Yii::getLogger().
When
enough messages are logged or when the application ends, the
logger object will call a
message dispatcher
to send recorded
log messages to the registered log targets.
4.8.2 Log Targets
A log target is an instance of the
yii\log\Target
class or its child class.
It lters the log messages by their severity levels and categories and then
database target exports
the ltered log messages to a database table, while an email target exports
exports them to some medium. For example, a
the log messages to specied email addresses.
You can register multiple log targets in an application by conguring
them through the
log application component in the application conguration,
like the following:
return [
// the "log" component must be loaded during bootstrapping time
'bootstrap' => ['log'],
'components' => [
'log' => [
'targets' => [
[
'class' => 'yii\log\DbTarget',
'levels' => ['error', 'warning'],
],
[
'class' => 'yii\log\EmailTarget',
180
CHAPTER 4.
'levels' => ['error'],
'categories' => ['yii\db\*'],
'message' => [
'from' => ['
[email protected]'],
'to' => ['
[email protected]', '
[email protected]'
],
];
],
HANDLING REQUESTS
],
],
],
Note: The
],
log
'subject' => 'Database errors at example.com',
component must be loaded during bootstrapping
time so that it can dispatch log messages to targets promptly.
That is why it is listed in the
bootstrap
array as shown above.
In the above code, two log targets are registered in the
::$targets property:
• the rst target selects
yii\log\Dispatcher
error and warning messages and saves them in
a database table;
•
the second target selects error messages under the categories whose
yii\db\, and sends them in an email to both admin@example
[email protected].
names start with
.com
and
Yii comes with the following built-in log targets.
Please refer to the API
documentation about these classes to learn how to congure and use them.
• yii\log\DbTarget: stores log messages in a database table.
• yii\log\EmailTarget: sends log messages to pre-specied email
ad-
dresses.
• yii\log\FileTarget: saves log messages in les.
• yii\log\SyslogTarget: saves log messages to syslog
PHP function
by calling the
syslog().
In the following, we will describe the features common to all log targets.
Message Filtering
For each log target, you can congure its
levels and categories properties
to specify which severity levels and categories of the messages the target
should process.
The
levels
property takes an array consisting of one or several of the
following values:
•
•
•
•
error: corresponding to messages logged by Yii::error().
warning: corresponding to messages logged by Yii::warning().
info: corresponding to messages logged by Yii::info().
trace: corresponding to messages logged by Yii::trace().
4.8.
LOGGING
181
• profile: corresponding to
and Yii::endProfile(),
messages logged by
Yii::beginProfile()
which will be explained in more details in
the Proling subsection.
If you do not specify the
messages of
The
any
levels
property, it means the target will process
severity level.
categories
property takes an array consisting of message category
names or patterns. A target will only process messages whose category can
be found or match one of the patterns in this array.
is a category name prex with an asterisk
*
A category pattern
at its end.
A category name
matches a category pattern if it starts with the same prex of the pattern.
For example,
yii\db\Command::execute
yii\db\Command::query
and
category names for the log messages recorded in the
They both match the pattern
If you do not specify the
process messages of
any
are used as
yii\db\Command
class.
yii\db\*.
categories
property, it means the target will
category.
categories property, you may
except property. If the category of
Besides whitelisting the categories by the
also blacklist certain categories by the
a message is found or matches one of the patterns in this property, it will
NOT be processed by the target.
The following target conguration species that the target should only
process error and warning messages under the categories whose names match
either
[
]
yii\db\*
or
yii\web\HttpException:*,
but not
yii\web\HttpException:404.
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
'categories' => [
'yii\db\*',
'yii\web\HttpException:*',
],
'except' => [
'yii\web\HttpException:404',
],
Info:
When an HTTP exception is caught by the error han-
dler, an error message will be logged with the category name in
the format of
yii\web\HttpException:ErrorCode.
For example, the
yii\web\NotFoundHttpException will cause an error message of
category
yii\web\HttpException:404.
Message Formatting
Log targets export the ltered log messages in a certain format. For example,
if you install a log target of the class
yii\log\FileTarget,
log message similar to the following in the
you may nd a
runtime/log/app.log
le:
182
CHAPTER 4.
HANDLING REQUESTS
2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading
module: debug
By default, log messages will be formatted as follows by the
::formatMessage():
yii\log\Target
Timestamp [IP address][User ID][Session ID][Severity Level][Category]
Message Text
You may customize this format by conguring the
yii\log\Target::$prefix
property which takes a PHP callable returning a customized message prex.
For example, the following code congures a log target to prex each log
message with the current user ID (IP address and Session ID are removed
for privacy reasons).
[
]
'class' => 'yii\log\FileTarget',
'prefix' => function ($message) {
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null
;
$userID = $user ? $user->getId(false) : '-';
return "[$userID]";
}
Besides message prexes, log targets also append some context information to each batch of log messages.
PHP variables are included:
$_SERVER.
By default, the values of these global
$_GET, $_POST, $_FILES, $_COOKIE, $_SESSION
You may adjust this behavior by conguring the
::$logVars
and
yii\log\Target
property with the names of the global variables that you want
to include by the log target. For example, the following log target conguration species that only the value of the
$_SERVER
variable will be appended
to the log messages.
[
]
'class' => 'yii\log\FileTarget',
'logVars' => ['_SERVER'],
You may congure
logVars
to be an empty array to totally disable the in-
clusion of context information. Or if you want to implement your own way
of providing context information, you may override the
getContextMessage()
yii\log\Target::
method.
Message Trace Level
During development, it is often desirable to see where each log message is
coming from. This can be achieved by conguring the
of the
log
component like the following:
return [
'bootstrap' => ['log'],
traceLevel
property
4.8.
];
LOGGING
183
'components' => [
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [...],
],
],
The above application conguration sets
on and 0 if
YII_DEBUG
traceLevel
is o. This means, if
YII_DEBUG
to be 3 if
YII_DEBUG
is
is on, each log message
will be appended with at most 3 levels of the call stack at which the log
message is recorded; and if
YII_DEBUG
is o, no call stack information will be
included.
Info:
Getting call stack information is not trivial.
Therefore,
you should only use this feature during development or when
debugging an application.
Message Flushing and Exporting
As aforementioned, log messages are maintained in an array by the
object.
logger
To limit the memory consumption by this array, the logger will
ush the recorded messages to the log targets each time the array accumulates a certain number of log messages. You can customize this number by
conguring the
flushInterval
property of the
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 100,
'targets' => [...],
],
],
];
Info:
log
component:
// default is 1000
Message ushing also occurs when the application ends,
which ensures log targets can receive complete log messages.
When the
logger object
ushes log messages to log targets, they do not
get exported immediately. Instead, the message exporting only occurs when
a log target accumulates certain number of the ltered messages. You can
customize this number by conguring the
exportInterval property of indi-
vidual log targets, like the following,
[
]
'class' => 'yii\log\FileTarget',
'exportInterval' => 100, // default is 1000
184
CHAPTER 4.
HANDLING REQUESTS
Because of the ushing and exporting level setting, by default when you call
Yii::trace()
or any other logging method, you will NOT see the log message
immediately in the log targets.
This could be a problem for some long-
running console applications. To make each log message appear immediately
in the log targets, you should set both
flushInterval and exportInterval
to be 1, as shown below:
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 1,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 1,
],
],
],
],
];
Note: Frequent message ushing and exporting will degrade the
performance of your application.
Toggling Log Targets
You can enable or disable a log target by conguring its
enabled
property.
You may do so via the log target conguration or by the following PHP
statement in your code:
Yii::$app->log->targets['file']->enabled = false;
The above code requires you to name a target as
using string keys in the
targets
file,
array:
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
],
'db' => [
'class' => 'yii\log\DbTarget',
],
],
],
],
];
as shown below by
4.8.
LOGGING
185
Creating New Targets
Creating a new log target class is very simple. You mainly need to implement
yii\log\Target::export() method sending the content of the yii\log
\Target::$messages array to a designated medium. You may call the yii
\log\Target::formatMessage() method to format each message. For more
the
details, you may refer to any of the log target classes included in the Yii
release.
4.8.3 Performance Proling
Performance proling is a special type of message logging that is used to
measure the time taken by certain code blocks and nd out what are the
performance bottlenecks. For example, the
yii\db\Command
class uses per-
formance proling to nd out the time taken by each DB query.
To use performance proling, rst identify the code blocks that need to
be proled. Then enclose each code block like the following:
\Yii::beginProfile('myBenchmark');
...code block being profiled...
\Yii::endProfile('myBenchmark');
where
myBenchmark
stands for a unique token identifying a code block. Later
when you examine the proling result, you will use this token to locate the
time spent by the corresponding code block.
It is important to make sure that the pairs of
beginProfile
and
endProfile
are properly nested. For example,
\Yii::beginProfile('block1');
// some code to be profiled
\Yii::beginProfile('block2');
// some other code to be profiled
\Yii::endProfile('block2');
\Yii::endProfile('block1');
If you miss
('block1')
\Yii::endProfile('block1') or switch the order of \Yii::endProfile
\Yii::endProfile('block2'), the performance proling will not
and
work.
For each code block being proled, a log message with the severity level
profile
is recorded. You can congure a log target to collect such messages
and export them.
The Yii debugger has a built-in performance proling
panel showing the proling results.
186
CHAPTER 4.
HANDLING REQUESTS
Chapter 5
Key Concepts
5.1 Components
Components are the main building blocks of Yii applications. Components
are instances of
yii\base\Component, 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 customizable and easier to use. For example, the included
date picker widget,
a user interface component, can be used in a view to generate an interactive
date picker:
use yii\jui\DatePicker;
echo DatePicker::widget([
'language' => 'ru',
'name' => 'country',
'clientOptions' => [
'dateFormat' => 'yy-mm-dd',
],
]);
The widget's properties are easily writable because the class extends
\base\Component.
yii
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
yii\base\Object instead of yii\base\Component.
Doing so will make
your components as ecient as normal PHP objects, but with added support
for properties.
187
188
CHAPTER 5.
When extending your class from
KEY CONCEPTS
yii\base\Component or yii\base\Object,
it is recommended that you follow these conventions:
•
If you override the constructor, specify a
structor's
last
parameter as the con-
parameter, and then pass this parameter to the parent
constructor.
•
$config
Always call the parent constructor
at the end
of your overriding con-
structor.
•
If you override the
yii\base\Object::init() method, make sure you
call the parent implementation of
init
at the beginning
of your
init
method.
For example:
<?php
namespace yii\components\MyClass;
use yii\base\Object;
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 congurable 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]);
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.
5.2.
PROPERTIES
The
yii\base\Object
189
class enforces the following object lifecycle:
1. Pre-initialization within the constructor. You can set default property
values here.
2. Object conguration via
$config.
The conguration may overwrite the
default values set within the constructor.
3. Post-initialization within
init().
You may override this method to
perform sanity checks and normalization of the properties.
4. Object method calls.
The rst 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 denition, and are used to represent the state of a class
instance (i.e., to dierentiate 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
where in your code where you might set the
the
label
label
trim()
every-
property. If, in the future,
property gets a new requirement, such as the rst letter must be
capitalized, 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
that supports dening properties based on
getter
and
yii\base\Object
setter
If a class needs that functionality, it should extend from
class methods.
yii\base\Object,
or from a child class.
Info: Nearly every core class in the Yii framework extends from
yii\base\Object
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.
190
CHAPTER 5.
KEY CONCEPTS
get; a setter
get or set prex denes the name
getLabel() and/or a setter setLabel()
A getter method is a method whose name starts with the word
method starts with
of a property.
set.
The name after the
For example, a getter
denes a property named
label,
as shown in the following code:
namespace app\components;
use yii\base\Object;
class Foo extends Object
{
private $_label;
public function getLabel()
{
return $this->_label;
}
}
public function setLabel($value)
{
$this->_label = trim($value);
}
label,
_label.)
(To be clear, the getter and setter methods create the property
in this case internally refers to a private attribute named
which
Properties dened by getters and setters can be used like class member
variables.
The main dierence is that when such property is being read,
the corresponding getter method will be called; when the property is being 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 dened by a getter without a setter is
a value to such a property will cause an
read only.
Trying to assign
InvalidCallException.
a property dened by a setter without a getter is
write only,
Similarly,
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
dened via getters and setters:
•
The names of such properties are
case-insensitive.
->label and $object->Label are the same.
For example,
$object
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
5.3.
EVENTS
191
a member variable
will aect the
setLabel()
•
label,
then the assignment
member variable
$object->label = 'abc'
`label'; that line would not call the
setter method.
These properties do not support visibility. It makes no dierence to
the dening getter or setter method if the property is public, protected
or private.
•
The properties can only be dened by
non-static getters and/or setters.
Static methods will not be treated in the same manner.
Returning back to the problem described at the beginning of this guide,
instead of calling
trim()
label value is
setter setLabel().
everywhere a
only needs to be invoked within the
assigned,
trim()
now
And if a new require-
ment makes it necesary that the label be initially capitalized, the
setLabel()
method can quickly be modied without touching any other code. The one
change will universally aect 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\base\Component to support events.
If a class needs to trigger events, it should extend from yii\base\Component,
Yii introduces a base class called
or from a child class.
5.3.1 Event Handlers
1 that gets executed when the event it is
An event handler is a PHP callback
attached to is triggered. You can use any of the following callbacks:
•
a global PHP function specied as a string (without parentheses), e.g.,
'trim';
•
an object method specied as an array of an object and a method name
as a string (without parentheses), e.g.,
•
[$object, 'methodName'];
a static class method specied as an array of a class name and a method
name as a string (without parentheses), e.g.,
['ClassName', 'methodName
'];
•
an anonymous function, e.g.,
function ($event) { ... }.
The signature of an event handler is:
function ($event) {
// $event is an object of yii\base\Event or a child class
1
http://www.php.net/manual/en/language.types.callable.php
192
CHAPTER 5.
KEY CONCEPTS
}
Through the
$event
parameter, an event handler may get the following in-
formation about the event that occurred:
• event name
• event sender: the object whose trigger() method was called
• custom 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
on()
yii\base\Component::
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, ['app\components\Bar', 'methodName']);
// this handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
// event handling logic
});
You may also attach event handlers through congurations. For more details,
please refer to the Congurations section.
When attaching an event handler, you may provide additional data as
the third parameter to
yii\base\Component::on().
The data will be made
available to the handler when the event is triggered and the handler is called.
For example:
// 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
5.3.
EVENTS
193
handlers that follow it, it may set the
of the
$event
yii\base\Event::$handled property
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 rst, you may call
\Component::on(),
passing false for the fourth parameter
yii\base
$append:
$foo->on(Foo::EVENT_HELLO, function ($event) {
// ...
}, $data, false);
5.3.4 Triggering Events
Events are triggered by calling the
The method requires an
yii\base\Component::trigger() method.
event name, and optionally an event object that de-
scribes the parameters to be passed to the event handlers. For example:
namespace app\components;
use yii\base\Component;
use yii\base\Event;
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.
Tip: It is recommended to use class constants to represent event
names.
In the above example, the constant
sents the
hello
EVENT_HELLO
repre-
event. This approach has three benets. 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 declarations.
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
194
CHAPTER 5.
KEY CONCEPTS
handlers can know the particulars of the sent messages. To do so, you can
yii\base\Component
::trigger() method. The event object must be an instance of the yii\base
\Event class or a child class. For example:
provide an event object as the second parameter to the
namespace app\components;
use yii\base\Component;
use yii\base\Event;
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
yii\base\Component::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
yii\base\Component::off()
method. For example:
// the handler is a global function
$foo->off(Foo::EVENT_HELLO, 'function_name');
// 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, ['app\components\Bar', '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.
5.3.
EVENTS
195
To detach ALL handlers from an event, simply call
::off()
yii\base\Component
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 specic instance. Instead
of attaching an event handler to every instance, you may attach the handler
on the
class level
by calling the static method
yii\base\Event::on().
EVENT_AFTER_INSERT
For example, an Active Record object will trigger an
event whenever it inserts a new record into the database. In order to track
insertions done by
every
Active Record object, you may use the following
code:
use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT,
function ($event) {
Yii::trace(get_class($event->sender) . ' is inserted');
});
ActiveRecord,
EVENT_AFTER_INSERT event. In the
The event handler will be invoked whenever an instance of
or one of its child classes, triggers the
handler, you can get the object that triggered the event through
$event->
sender.
When an object triggers an event, it will rst call instance-level handlers,
followed by the class-level handlers.
You may trigger a
\Event::trigger().
class-level
event by calling the static method
yii\base
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:
use yii\base\Event;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
echo $event->sender; // displays "app\models\Foo"
});
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
196
CHAPTER 5.
KEY CONCEPTS
should use it carefully, especially if the class is a low-level base
class, such as
yii\base\Object.
To detach a class-level event handler, call
yii\base\Event::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 accessible Singleton, such as the application instance itself.
To create the global event, an event sender calls the Singleton's
method to trigger the event, instead of calling the sender's own
method.
trigger()
trigger()
Similarly, the event handlers are attached to the event on the
Singleton. For example:
use Yii;
use yii\base\Event;
use app\components\Foo;
Yii::$app->on('bar', function ($event) {
echo get_class($event->sender); // displays "app\components\Foo"
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
A benet of using global events is that you do not need an object when attaching 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).
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
yii\base\Behavior,
2
or of a child class. Behav-
iors, also known as mixins , allow you to enhance the functionality of an
existing
component
class without needing to change the class's inheritance.
Attaching a behavior to a component injects the behavior's methods and
2
http://en.wikipedia.org/wiki/Mixin
5.4.
BEHAVIORS
197
properties into the component, making those methods and properties accessible as if they were dened 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 Dening Behaviors
yii\base\Behavior,
To dene a behavior, create a class that extends
or
extends a child class. For example:
namespace app\components;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
public $prop1;
private $_prop2;
public function getProp2()
{
return $this->_prop2;
}
public function setProp2($value)
{
$this->_prop2 = $value;
}
}
public function foo()
{
// ...
}
app\components\MyBehavior, with
prop2and one method foo(). Note that property
prop2 is dened via the getter getProp2() and the setter setProp2(). This is the
The above code denes the behavior class
two properties
case because
prop1
and
yii\base\Behavior
extends
yii\base\Object
and therefore
supports dening 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
the
foo()
prop1
and
prop2
properties and
method.
Tip:
Within a behavior, you can access the component that
the behavior is attached to through the
$owner
property.
yii\base\Behavior::
198
CHAPTER 5.
KEY CONCEPTS
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
yii\base\Behavior::events() method.
For example:
namespace app\components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// ...
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
public function beforeValidate($event)
{
// ...
}
}
The
events() method should return a list of events and their corresponding
The above example declares that the EVENT_BEFORE_VALIDATE
handlers.
event exists and denes 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
•
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
component
either statically or dynamically.
The former is more common in practice.
To attach a behavior statically, override the
behaviors() method of the
behaviors()
component class to which the behavior is being attached. The
5.4.
BEHAVIORS
199
method should return a list of behavior congurations. Each behavior conguration can be either a behavior class name or a conguration array:
namespace app\models;
use yii\db\ActiveRecord;
use app\components\MyBehavior;
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',
]
You may associate a name with a behavior by specifying the array key corresponding to the behavior conguration. In this case, the behavior is called
a
named behavior.
myBehavior2
is called an
and
In the above example, there are two named behaviors:
myBehavior4.
If a behavior is not associated with a name, it
anonymous behavior.
To attach a behavior dynamically, call the
yii\base\Component::attachBehavior()
method of the component to which the behavior is being attached:
use app\components\MyBehavior;
// 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', [
200
]);
CHAPTER 5.
KEY CONCEPTS
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
You may attach multiple behaviors at once using the
::attachBehaviors()
yii\base\Component
method:
$component->attachBehaviors([
'myBehavior1' => new MyBehavior, // a named behavior
MyBehavior::className(),
// an anonymous behavior
]);
You may also attach behaviors through congurations like the following:
[
]
'as myBehavior2' => MyBehavior::className(),
'as myBehavior3' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
],
For more details, please refer to the Congurations section.
5.4.4 Using Behaviors
To use a behavior, rst attach it to a
component
per the instructions above.
Once a behavior is attached to a component, its usage is straightforward.
You can access a
public member variable or a property dened 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 dene
prop1
and
foo(),
they can
be used as if they are part of the component denition due to the attached
behavior.
If two behaviors dene the same property or method and they are both
attached to the same component, the behavior that is attached to the component
rst
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');
5.4.
BEHAVIORS
201
You may also get all behaviors attached to a component:
$behaviors = $component->getBehaviors();
5.4.5 Detaching Behaviors
To detach a behavior, call
yii\base\Component::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
yii\behaviors\TimestampBehavior.
This
behavior supports automatically updating the timestamp attributes of an
Active Record model anytime the model is saved (e.g., on insert or update).
First, attach this behavior to the Active Record class that you plan to
use:
namespace app\models\User;
use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;
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 conguration above species that when the record is being:
•
inserted, the behavior should assign the current timestamp to the
•
updated, the behavior should assign the current timestamp to the
created_at
updated_at
and
updated_at
attribute
attributes
202
CHAPTER 5.
With that code in place, if you have a
nd its
created_at
and
updated_at
User
KEY CONCEPTS
object and try to save it, you will
are automatically lled with the current
timestamp:
$user = new User;
$user->email = '
[email protected]';
$user->save();
echo $user->created_at; // shows the current timestamp
The
TimestampBehavior
also oers a useful method
touch(),
which will
assign the current timestamp to a specied attribute and save it to the
database:
$user->touch('login_time');
5.4.7 Comparing Behaviors with Traits
3 in that they both inject their prop-
While behaviors are similar to traits
erties and methods to the primary class, they dier in many aspects.
As
explained below, they both have pros and cons. They are more like complements to each other rather than alternatives.
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 modication of the component class. To use a trait, you
must modify the code of the class using it.
Behaviors are congurable while traits are not.
Behaviors can customize the code execution of a component by responding to its events.
When there can be name conicts among dierent behaviors attached to
the same component, the conicts are automatically resolved by prioritizing
the behavior attached to the component rst. Name conicts caused by different traits requires manual resolution by renaming the aected properties
or methods.
Reasons to Use Traits
Traits are much more ecient than behaviors as behaviors are objects that
take both time and memory.
IDEs are more friendly to traits as they are a native language construct.
3
http://www.php.net/traits
5.5.
CONFIGURATIONS
203
5.5 Congurations
Congurations are widely used in Yii when creating new objects or initializing existing objects. Congurations 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. Congurations 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 conguration is used to create and initialize a database
connection:
$config = [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
$db = Yii::createObject($config);
Yii::createObject()
The
method takes a conguration array as its argu-
ment, and creates an object by instantiating the class named in the conguration. When the object is instantiated, the rest of the conguration 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 conguration array:
Yii::configure($object, $config);
Note that, in this case, the conguration array should not contain a
class
element.
5.5.1 Conguration Format
The format of a conguration can be formally described as:
[
]
'class' => 'ClassName',
'propertyName' => 'propertyValue',
'on eventName' => $eventHandler,
'as behaviorName' => $behaviorConfig,
where
•
The
class
element species a fully qualied 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 corresponding initial values. Only public member variables and properties
dened by getters/setters can be congured.
204
CHAPTER 5.
•
The
on eventName
KEY CONCEPTS
elements specify what handlers should be attached to
the object's events. Notice that the array keys are formed by prexing
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 prexing behavior names with
as
; the value,
$behaviorConfig,
represents
the conguration for creating a behavior, like a normal conguration
described here.
Below is an example showing a conguration with initial property values,
event handlers, and behaviors:
[
]
'class' => 'app\components\SearchEngine',
'apiKey' => 'xxxxxxxx',
'on search' => function ($event) {
Yii::info("Keyword searched: " . $event->keyword);
},
'as indexer' => [
'class' => 'app\components\IndexerBehavior',
// ... property init values ...
],
5.5.2 Using Congurations
Congurations are used in many places in Yii.
At the beginning of this
section, we have shown how to create an object according to a conguration by using
Yii::createObject().
In this subsection, we will describe
application congurations and widget congurations - two major usages of
congurations.
Application Congurations
The conguration for an application is probably one of the most complex ar-
application
class has a lot of congurable
properties and events. More importantly, its
property can re-
rays in Yii. This is because the
components
ceive an array of congurations for creating components that are registered
through the application. The following is an abstract from the application
conguration le for the Basic Project Template.
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache',
],
5.5.
];
CONFIGURATIONS
],
205
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
'log' => [
'class' => 'yii\log\Dispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
The conguration 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 yii\web\Application($config))->run();
More details about conguring the
components
property of an application can
be found in the Applications section and the Service Locator section.
Widget Congurations
When using widgets, you often need to use congurations to customize the
widget properties. Both of the
\Widget::begin()
yii\base\Widget::widget()
and
methods can be used to create a widget.
yii\base
They take a
conguration array, like the following,
use yii\widgets\Menu;
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
to be false.
The
items
Menu widget and initializes its activateItems property
property is also congured with menu items to be
displayed.
Note that because the class name is already given, the conguration array
should NOT have the
class
key.
206
CHAPTER 5.
KEY CONCEPTS
5.5.3 Conguration Files
When a conguration is very complex, a common practice is to store it in
one or multiple PHP les, known as
conguration les.
A conguration le
returns a PHP array representing the conguration. For example, you may
keep an application conguration in a le named
web.php,
like the following,
return [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => require(__DIR__ . '/components.php'),
];
components conguration is complex too, you store it in a separate
components.php and require this le in web.php as shown above. The
of components.php is as follows,
Because the
le called
content
return [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
'log' => [
'class' => 'yii\log\Dispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
];
To get a conguration stored in a conguration le, simply require it, like
the following:
$config = require('path/to/web.php');
(new yii\web\Application($config))->run();
5.5.4 Default Congurations
The
Yii::createObject()
injection container.
method is implemented based on a dependency
It allows you to specify a set of the so-called
default
5.5.
CONFIGURATIONS
congurations
207
which will be applied to ALL instances of the specied classes
when they are being created using
Yii::createObject().
gurations can be specied by calling
The default con-
Yii::$container->set()
in the boot-
strapping code.
For example, if you want to customize
yii\widgets\LinkPager
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('yii\widgets\LinkPager', [
'maxButtonCount' => 5,
]);
Without using default congurations, you would have to congure
maxButtonCount
in every place where you use link pagers.
5.5.5 Environment Constants
Congurations often vary according to the environment in which an application runs. For example, in development environment, you may want to use
a database named
the
mydb_prod
mydb_dev,
database.
a constant named
while on production server you may want to use
To facilitate switching environments, Yii provides
YII_ENV
that you may dene in the entry script of your
application. For example,
defined('YII_ENV') or define('YII_ENV', 'dev');
You may dene
• prod:
YII_ENV
as one of the following values:
production environment. The constant
as true. This is the default value of
• dev:
YII_ENV
YII_ENV_PROD
will evaluate
if you do not dene it.
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 congurations conditionally based on the current environment. For example, your application
conguration 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'] = 'yii\debug\Module';
}
return $config;
208
CHAPTER 5.
KEY CONCEPTS
5.6 Aliases
Aliases are used to represent le 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 dierentiated from normal le paths and URLs. Yii
has many pre-dened aliases already available. For example, the alias
represents the installation path of the Yii framework;
@web
@yii
represents the
base URL for the currently running Web application.
5.6.1 Dening Aliases
You can dene an alias for a le 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 le path or URL being aliased may
not
necessarily
refer to an existing le or resource.
Given a dened alias, you may derive a new alias (without the need of calling
Yii::setAlias())
by appending a slash
segments. The aliases dened via
while aliases derived from it are
alias, while
@foo/bar/file.php
/
followed with one or more path
Yii::setAlias()
derived aliases.
becomes the
For example,
root alias,
@foo
is a root
is a derived alias.
You can dene an alias using another alias (either root or derived):
Yii::setAlias('@foobar', '@foo/bar');
Root aliases are usually dened during the bootstrapping stage. For example, you may call
Yii::setAlias()
in the entry script.
Application provides a writable property named
aliases
For convenience,
that you can con-
gure in the application conguration:
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 le path or
URL it represents. The same method can also resolve a derived alias into
the corresponding le path or URL:
5.6.
ALIASES
209
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 le or resource.
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 le 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
@foo/bar is not dened
/path/to/foo/bar/file.php.
If
as a root alias, the last statement would display
5.6.3 Using Aliases
Yii::
getAlias() to convert them into paths or URLs. For example, yii\caching
\FileCache::$cachePath can accept both a le path and an alias representAliases are recognized in many places in Yii without needing to call
ing a le path, thanks to the
@
prex which allows it to dierentiate a le
path from an alias.
use yii\caching\FileCache;
$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 Predened Aliases
Yii predenes a set of aliases to easily reference commonly used le paths
and URLs:
• @yii,
the directory where the
BaseYii.php
le is located (also called the
framework directory).
• @app, the base path of the currently running application.
• @runtime, the runtime path of the currently running application.
faults to
@app/runtime.
De-
210
CHAPTER 5.
• @webroot,
KEY CONCEPTS
the Web root directory of the currently running Web appli-
cation. It is determined based on the directory containing the entry
script.
• @web,
the base URL of the currently running Web application. It has
yii\web\Request::$baseUrl.
Composer vendor directory. Defaults
the same value as
• @vendor, the
• @bower, the root
@vendor/bower.
• @npm, the root directory that contains
@vendor/npm.
The @yii alias is dened when you include
script.
to
@app/vendor.
4
directory that contains bower packages . Defaults to
5
npm packages .
the
Yii.php
Defaults to
le in your entry
The rest of the aliases are dened in the application constructor
when applying the application conguration.
5.6.5 Extension Aliases
An alias is automatically dened 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
le, and each alias represents the root directory
of the package. For example, if you install the
will automatically have the alias
@yii/jui
yiisoft/yii2-jui extension, you
dened during the bootstrapping
stage, equivalent to:
Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui');
5.7 Class Autoloading
6 to locate and include all
Yii relies on the class autoloading mechanism
required class les. It provides a high-performance class autoloader that is
7
compliant with the PSR-4 standard . The autoloader is installed when you
include the
Yii.php
le.
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/
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/accepted/
PSR-4-autoloader.md
5
5.7.
CLASS AUTOLOADING
•
•
211
8 (e.g. foo\bar\MyClass)
Each class must be under a namespace
Each class must be saved in an individual le whose path is determined
by the following algorithm:
// $className is a fully qualified class name without the leading backslash
$classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php'
);
foo\bar\MyClass, the alias for
@foo/bar/MyClass.php. In order for
either @foo or @foo/bar must be a
For example, if a class name and namespace is
the corresponding class le path would be
this alias to be resolvable into a le path,
root alias.
When using the Basic Project Template, you may put your classes under
the top-level namespace
app
so that they can be autoloaded by Yii with-
This is because @app is a predened
app\components\MyClass can be resolved into the
AppBasePath/components/MyClass.php, according to the algorithm just
out the need of dening a new alias.
alias, and a class name like
class le
described.
9
In the Advanced Project Template , each tier has its own root alias. For
@frontend, while the back-end tier
@backend. As a result, you may put the front-end classes under
namespace frontend while the back-end classes are under backend. This
example, the front-end tier has a root alias
root alias is
the
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 le paths. When the autoloader is loading a
class, it will rst check if the class is found in the map. If so, the corresponding le path will be included directly without further checks.
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['foo\bar\MyClass'] = 'path/to/MyClass.php';
Aliases can be used to specify class le 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.
8
If you are
http://php.net/manual/en/language.namespaces.php
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
9
212
CHAPTER 5.
KEY CONCEPTS
using 3rd-party libraries that have their own autoloaders, you should also
install those.
When using the Yii autoloader together with other autoloaders, you
should include the
Yii.php
le
after
all other autoloaders are installed. This
will make the Yii autoloader the rst one responding to any class autoloading
request. For example, the following code is extracted from the entry script of
the Basic Project Template. The rst 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
le and include it in your
entry script.
5.7.4 Autoloading Extension Classes
The Yii autoloader is capable of autoloading extension classes.
requirement is that an extension species the
composer.json
autoload
le. Please refer to the Composer documentation
details about specifying
The sole
section correctly in its
10 for more
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 identied 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
yii\di\ServiceLocator
or a child class.
The most commonly used service locator in Yii is the
application
object,
\Yii::$app. The services it provides are called
as the request, response, and urlManager compo-
which can be accessed through
application components, such
nents. You may congure these components, or even replace them with your
own implementations, easily through functionality provided by the service
locator.
10
https://getcomposer.org/doc/04-schema.md#autoload
5.8.
SERVICE LOCATOR
213
Besides the application object, each module object is also a service locator.
To use a service locator, the rst step is to register components with it.
A component can be registered via
yii\di\ServiceLocator::set().
The
following code shows dierent ways of registering components:
use yii\di\ServiceLocator;
use yii\caching\FileCache;
$locator = new ServiceLocator;
// register "cache" using a class name that can be used to create a
component
$locator->set('cache', 'yii\caching\ApcCache');
// register "db" using a configuration array that can be used to create a
component
$locator->set('db', [
'class' => 'yii\db\Connection',
'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 app\components\SolrService;
});
// 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,
yii\di\ServiceLocator allows you to access a component
like a property using the component ID. When you access a component for
the rst time,
yii\di\ServiceLocator
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.
yii\di\ServiceLocator::has() to check if a component
ID has already been registered. If you call yii\di\ServiceLocator::get()
You may use
with an invalid ID, an exception will be thrown.
Because service locators are often being created with congurations, a
writable property named
components
is provided. This allows you to con-
gure and register multiple components at once. The following code shows
214
CHAPTER 5.
KEY CONCEPTS
a conguration array that can be used to congure a service locator (e.g. an
application) with the db, cache and search components:
return [
// ...
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
],
'cache' => 'yii\caching\ApcCache',
'search' => function () {
$solr = new app\components\SolrService('127.0.0.1');
// ... other initializations ...
return $solr;
},
],
];
In the above, there is an alternative way to congure the search component. Instead of directly writing a PHP callback which builds a
SolrService
instance, you can use a static class method to return such a callback, like
shown as below:
class SolrServiceBuilder
{
public static function build($ip)
{
return function () use ($ip) {
$solr = new app\components\SolrService($ip);
// ... other initializations ...
return $solr;
};
}
}
return [
// ...
'components' => [
// ...
'search' => SolrServiceBuilder::build('127.0.0.1'),
],
];
This alternative approach is most preferable when you are releasing a Yii
component which encapsulates some non-Yii 3rd-party library. You use the
static method like shown above to represent the complex logic of building
the 3rd-party object, and the user of your component only needs to call the
static method to congure the component.
5.9.
DEPENDENCY INJECTION CONTAINER
215
5.9 Dependency Injection Container
A dependency injection (DI) container is an object that knows how to instantiate and congure objects and all their dependent objects.
Martin's
11 has well explained why DI container is useful. Here we will mainly
article
explain the usage of the DI container provided by Yii.
5.9.1 Dependency Injection
Yii provides the DI container feature through the class
yii\di\Container.
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)
{
}
}
$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 congurations.
When
registering a dependency or when creating a new object, you can provide a
conguration which will be used by the container to inject the dependencies
through the corresponding setters or properties. For example,
use yii\base\Object;
class Foo extends Object
{
11
http://martinfowler.com/articles/injection.html
216
CHAPTER 5.
KEY CONCEPTS
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'),
]);
Info: The
yii\di\Container::get() method takes its third pa-
rameter as a conguration array that should be applied to the ob-
yii\base\Configurable
yii\base\Object), the conguration array will be
ject being created. If the class implements the
interface (e.g.
passed as the last parameter to the class constructor; otherwise,
the conguration will be applied
after
the object is created.
PHP Callable Injection
In this case, the container will use a registered PHP callable to build new
instances of a class. Each time when
yii\di\Container::get()
the corresponding callable will be invoked.
is called,
The callable is responsible to
resolve the dependencies and inject them appropriately to the newly created
objects. For example,
$container->set('Foo', function () {
$foo = new Foo(new Bar);
// ... other initializations ...
return $foo;
});
$foo = $container->get('Foo');
To hide the complex logic for building a new object, you may use a static
class method to return the PHP callable. For example,
class FooBuilder
{
public static function build()
{
return function () {
5.9.
}
DEPENDENCY INJECTION CONTAINER
}
};
217
$foo = new Foo(new Bar);
// ... other initializations ...
return $foo;
$container->set('Foo', FooBuilder::build());
$foo = $container->get('Foo');
As you can see, the PHP callable is returned by the
FooBuilder::build()
Foo class no
method. By doing so, the person who wants to congure the
longer needs to be aware of how it is built.
5.9.2 Registering Dependencies
You can use
yii\di\Container::set()
to register dependencies. The reg-
istration requires a dependency name as well as a dependency denition. A
dependency name can be a class name, an interface name, or an alias name;
and a dependency denition can be a class name, a conguration array, or a
PHP callable.
$container = new \yii\di\Container;
// register a class name as is. This can be skipped.
$container->set('yii\db\Connection');
// register an interface
// When a class depends on the interface, the corresponding class
// will be instantiated as the dependent object
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
// register an alias name. You can use $container->get('foo')
// to create an instance of Connection
$container->set('foo', 'yii\db\Connection');
// register a class with configuration. The configuration
// will be applied when the class is instantiated by get()
$container->set('yii\db\Connection', [
'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' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
218
]);
CHAPTER 5.
KEY CONCEPTS
'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 \yii\db\Connection($config);
});
// register a component instance
// $container->get('pageCache') will return the same instance each time it
is called
$container->set('pageCache', new FileCache);
Tip:
If a dependency name is the same as the corresponding
dependency denition, you do not need to register it with the DI
container.
A dependency registered via
dependency is needed.
set()
will generate an instance each time the
You can use
yii\di\Container::setSingleton()
to register a dependency that only generates a single instance:
$container->setSingleton('yii\db\Connection', [
'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
yii\di\Container::get()
to create new objects.
The
method takes a dependency name, which can be a class name, an interface
name or an alias name.
tered via
set()
or
The dependency name may or may not be regis-
setSingleton().
You may optionally provide a list of class
constructor parameters and a conguration to congure the newly created
object. For example,
// "db" is a previously registered alias name
$db = $container->get('db');
// equivalent to: $engine = new \app\components\SearchEngine($apiKey, ['type
' => 1]);
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type'
=> 1]);
5.9.
DEPENDENCY INJECTION CONTAINER
219
Behind the scene, the DI container does much more work than just creating
a new object. The container will rst inspect the class constructor to nd
out dependent class or interface names and then automatically resolve those
dependencies recursively.
The following code shows a more sophisticated example. The
class depends on an object implementing the
the
UserFinder
object.
UserFinderInterface
class implements this interface and depends on a
UserLister
interface;
Connection
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').
namespace app\models;
use yii\base\Object;
use yii\db\Connection;
use yii\di\Container;
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('yii\db\Connection', [
'dsn' => '...',
220
CHAPTER 5.
KEY CONCEPTS
]);
$container->set('app\models\UserFinderInterface', [
'class' => 'app\models\UserFinder',
]);
$container->set('userLister', 'app\models\UserLister');
$lister = $container->get('userLister');
// which is equivalent to:
$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);
5.9.4 Practical Usage
Yii creates a DI container when you include the
Yii.php le in the entry script
The DI container is accessible via Yii::$container.
Yii::createObject(), the method will actually call the con-
of your application.
When you call
tainer's
get()
method to create a new object. As aforementioned, 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
yii\widgets\LinkPager:
\Yii::$container->set('yii\widgets\LinkPager', ['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 dened in
the class.
echo \yii\widgets\LinkPager::widget();
You can still override the value set via DI container, though:
echo \yii\widgets\LinkPager::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 app\controllers;
use yii\web\Controller;
use app\components\BookingInterface;
class HotelController extends Controller
{
5.9.
DEPENDENCY INJECTION CONTAINER
221
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
BookingInterface
the
cannot be instantiated. This is because you need to tell
the DI container how to deal with this dependency:
\Yii::$container->set('app\components\BookingInterface', 'app\components\
BookingService');
Now if you access the controller again, an instance of
app\components\BookingService
will be created and injected as the 3rd parameter to the controller's constructor.
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 fashion.
12 to get a deeper
We highly recommend you to read Martin's article
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.
12
http://martinfowler.com/articles/injection.html
222
CHAPTER 5.
KEY CONCEPTS
Chapter 6
Working with Databases
6.1 Database Access Objects
1
Built on top of PDO , Yii DAO (Database Access Objects) provides an
object-oriented API for accessing relational databases. It is the foundation
for other more advanced database access methods, including query builder
and active record.
When using Yii DAO, you mainly need to deal with plain SQLs and
PHP arrays.
As a result, it is the most ecient way to access databases.
However, because SQL syntax may vary for dierent databases, using Yii
DAO also means you have to take extra eort to create a database-agnostic
application.
Yii DAO supports the following databases out of box:
•
•
•
•
•
•
•
2
MySQL
3
MariaDB
4
SQLite
PostgreSQL
5
6
CUBRID : version 9.3 or higher.
7
Oracle
8
MSSQL : version 2008 or higher.
6.1.1 Creating DB Connections
To access a database, you rst need to connect to it by creating an instance
of
yii\db\Connection:
1
http://www.php.net/manual/en/book.pdo.php
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
2
223
224
CHAPTER 6.
WORKING WITH DATABASES
$db = new yii\db\Connection([
'dsn' => 'mysql:host=localhost;dbname=example',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
Because a DB connection often needs to be accessed in dierent places, a
common practice is to congure it in terms of an application component like
the following:
return [
// ...
'components' => [
// ...
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=example',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
],
// ...
];
You can then access the DB connection via the expression
Yii::$app->db.
Tip: You can congure multiple DB application components if
your application needs to access multiple databases.
When conguring a DB connection, you should always specify its Data
Source Name (DSN) via the
dsn
property.
The format of DSN varies for
dierent databases. Please refer to the PHP manual
9 for more details. Be-
low are some examples:
mysql:host=localhost;dbname=mydatabase
sqlite:/path/to/database/file
PostgreSQL: pgsql:host=localhost;port=5432;dbname=mydatabase
CUBRID: cubrid:dbname=demodb;host=localhost;port=33000
MS SQL Server (via sqlsrv driver): sqlsrv:Server=localhost;Database=
mydatabase
• MS SQL Server (via dblib driver): dblib:host=localhost;dbname=mydatabase
•
•
•
•
•
MySQL, MariaDB:
SQLite:
•
MS SQL Server (via mssql driver):
•
Oracle:
mssql:host=localhost;dbname=mydatabase
oci:dbname=//localhost:1521/mydatabase
Note that if you are connecting with a database via ODBC, you should
congure the
yii\db\Connection::$driverName
property so that Yii can
know the actual database type. For example,
9
http://www.php.net/manual/en/function.PDO-construct.php
6.1.
DATABASE ACCESS OBJECTS
225
'db' => [
'class' => 'yii\db\Connection',
'driverName' => 'mysql',
'dsn' => 'odbc:Driver={MySQL};Server=localhost;Database=test',
'username' => 'root',
'password' => '',
],
dsn property, you often need to congure username and password.
Please refer to yii\db\Connection for the full list of congurable properties.
Besides the
Info:
When you create a DB connection instance, the actual
connection to the database is not established until you execute
the rst SQL or you call the
open()
method explicitly.
6.1.2 Executing SQL Queries
Once you have a database connection instance, you can execute a SQL query
by taking the following steps:
1. Create a
yii\db\Command
with a plain SQL;
2. Bind parameters (optional);
3. Call one of the SQL execution methods in
yii\db\Command.
The following example shows various ways of fetching data from a database:
$db = new yii\db\Connection(...);
// return a set of rows. each row is an associative array of column names
and values.
// an empty array is returned if no results
$posts = $db->createCommand('SELECT * FROM post')
->queryAll();
// return a single row (the first row)
// false is returned if no results
$post = $db->createCommand('SELECT * FROM post WHERE id=1')
->queryOne();
// return a single column (the first column)
// an empty array is returned if no results
$titles = $db->createCommand('SELECT title FROM post')
->queryColumn();
// return a scalar
// false is returned if no results
$count = $db->createCommand('SELECT COUNT(*) FROM post')
->queryScalar();
226
CHAPTER 6.
Note:
WORKING WITH DATABASES
To preserve precision, the data fetched from databases
are all represented as strings, even if the corresponding database
column types are numerical.
Tip: If you need to execute a SQL query right after establishing
a connection (e.g., to set the timezone or character set), you
can do so in the
yii\db\Connection::EVENT_AFTER_OPEN event
handler. For example,
return [
// ...
'components' => [
// ...
'db' => [
'class' => 'yii\db\Connection',
// ...
'on afterOpen' => function($event) {
// $event->sender refers to the DB connection
$event->sender->createCommand("SET time_zone = '
UTC'")->execute();
}
],
],
// ...
];
Binding Parameters
When creating a DB command from a SQL with parameters, you should
almost always use the approach of binding parameters to prevent SQL injection attacks. For example,
$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:
status')
->bindValue(':id', $_GET['id'])
->bindValue(':status', 1)
->queryOne();
In the SQL statement, you can embed one or multiple parameter placeholders
(e.g.
:id
in the above example). A parameter placeholder should be a string
starting with a colon.
You may then call one of the following parameter
binding methods to bind the parameter values:
• bindValue(): bind a single parameter value
• bindValues(): bind multiple parameter values in one call
• bindParam(): similar to bindValue() but also support binding
rameter references.
The following example shows alternative ways of binding parameters:
$params = [':id' => $_GET['id'], ':status' => 1];
pa-
6.1.
DATABASE ACCESS OBJECTS
227
$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:
status')
->bindValues($params)
->queryOne();
$post = $db->createCommand('SELECT * FROM post WHERE id=:id AND status=:
status', $params)
->queryOne();
Parameter binding is implemented via prepared statements
10 . Besides pre-
venting SQL injection attacks, it may also improve performance by preparing
a SQL statement once and executing it multiple times with dierent parameters. For example,
$command = $db->createCommand('SELECT * FROM post WHERE id=:id');
$post1 = $command->bindValue(':id', 1)->queryOne();
$post2 = $command->bindValue(':id', 2)->queryOne();
Because
bindParam() supports binding parameters by references,
the above
code can also be written like the following:
$command = $db->createCommand('SELECT * FROM post WHERE id=:id')
->bindParam(':id', $id);
$id = 1;
$post1 = $command->queryOne();
$id = 2;
$post2 = $command->queryOne();
Notice that you bind the placeholder to the
$id variable before
the execution,
and then change the value of that variable before each subsequent execution
(this is often done with loops).
Executing queries in this manner can be
vastly more ecient than running a new query for every dierent parameter
value.
Executing Non-SELECT Queries
The
queryXyz()
methods introduced in the previous sections all deal with
SELECT queries which fetch data from databases. For queries that do not
bring back data, you should call the
yii\db\Command::execute()
method
instead. For example,
$db->createCommand('UPDATE post SET status=1 WHERE id=1')
->execute();
The
yii\db\Command::execute()
method returns the number of rows af-
fected by the SQL execution.
For INSERT, UPDATE and DELETE queries, instead of writing plain
SQLs, you may call
10
insert(), update(), delete(), respectively, to build the
http://php.net/manual/en/mysqli.quickstart.prepared-statements.php
228
CHAPTER 6.
WORKING WITH DATABASES
corresponding SQLs. These methods will properly quote table and column
names and bind parameter values. For example,
// INSERT (table name, column values)
$db->createCommand()->insert('user', [
'name' => 'Sam',
'age' => 30,
])->execute();
// UPDATE (table name, column values, condition)
$db->createCommand()->update('user', ['status' => 1], 'age > 30')->execute()
;
// DELETE (table name, condition)
$db->createCommand()->delete('user', 'status = 0')->execute();
You may also call
batchInsert() to insert multiple rows in one shot,
which
is much more ecient than inserting one row at a time:
// table name, column names, column values
$db->createCommand()->batchInsert('user', ['name', 'age'], [
['Tom', 30],
['Jane', 20],
['Linda', 25],
])->execute();
6.1.3 Quoting Table and Column Names
When writing database-agnostic code, properly quote table and column
names is often a headache because dierent databases have dierent name
quoting rules. To overcome this problem, you may use the following quoting
syntax introduced by Yii:
• [[column name]]:
enclose a column name to be quoted in double square
brackets;
• {{table name}}:
enclose a table name to be quoted in double curly
brackets.
Yii DAO will automatically turn such constructs in a SQL into the corresponding quoted column or table names. For example,
// executes this SQL for MySQL: SELECT COUNT(`id`) FROM `employee`
$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}")
->queryScalar();
Using Table Prex
If most of your DB tables use some common prex in their tables, you may
use the table prex feature supported by Yii DAO.
First, specify the table prex via the
property:
yii\db\Connection::$tablePrefix
6.1.
DATABASE ACCESS OBJECTS
229
return [
// ...
'components' => [
// ...
'db' => [
// ...
'tablePrefix' => 'tbl_',
],
],
];
Then in your code, whenever you need to refer to a table whose name contains
such a prex, use the syntax
{{%table name}}.
The percentage character will
be automatically replaced with the table prex that you have specied when
conguring the DB connection. For example,
// executes this SQL for MySQL: SELECT COUNT(`id`) FROM `tbl_employee`
$count = $db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}")
->queryScalar();
6.1.4 Performing Transactions
When running multiple related queries in a sequence, you may need to
wrap them in a transaction to ensure the integrity and consistency of your
database. If any of the queries fails, the database will be rolled back to the
state as if none of these queries is executed.
The following code shows a typical way of using transactions:
$db->transaction(function($db) {
$db->createCommand($sql1)->execute();
$db->createCommand($sql2)->execute();
// ... executing other SQL statements ...
});
The above code is equivalent to the following:
$transaction = $db->beginTransaction();
try {
$db->createCommand($sql1)->execute();
$db->createCommand($sql2)->execute();
// ... executing other SQL statements ...
$transaction->commit();
} catch(\Exception $e) {
$transaction->rollBack();
}
throw $e;
By calling the
beginTransaction()
The transaction is represented as a
method, a new transaction is started.
yii\db\Transaction
object stored in
230
CHAPTER 6.
WORKING WITH DATABASES
$transaction variable. Then, the queries being executed are enclosed in
try...catch... block. If all queries are executed successfully, the commit()
the
a
method is called to commit the transaction. Otherwise, an exception will be
triggered and caught, and the
rollBack()
method is called to roll back the
changes made by the queries prior to that failed query in the transaction.
Specifying Isolation Levels
Yii also supports setting isolation levels
11 for your transactions. By default,
when starting a new transaction, it will use the isolation level set by your
database system. You can override the default isolation level as follows,
$isolationLevel = \yii\db\Transaction::REPEATABLE_READ;
$db->transaction(function ($db) {
....
}, $isolationLevel);
// or alternatively
$transaction = $db->beginTransaction($isolationLevel);
Yii provides four constants for the most common isolation levels:
• yii\db\Transaction::READ_UNCOMMITTED
- the weakest level, Dirty
reads, non-repeatable reads and phantoms may occur.
• yii\db\Transaction::READ_COMMITTED - avoid dirty reads.
• yii\db\Transaction::REPEATABLE_READ - avoid dirty reads and nonrepeatable reads.
• yii\db\Transaction::SERIALIZABLE
- the strongest level, avoids all
of the above named problems.
Besides using the above constants to specify isolation levels, you may also
use strings with a valid syntax supported by the DBMS that you are using.
For example, in PostgreSQL, you may use
SERIALIZABLE READ ONLY DEFERRABLE.
Note that some DBMS allow setting the isolation level only for the whole
connection.
Any subsequent transactions will get the same isolation level
even if you do not specify any. When using this feature you may need to set
the isolation level for all transactions explicitly to avoid conicting settings.
At the time of this writing, only MSSQL and SQLite are aected.
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 being thrown.
Note: PostgreSQL does not allow setting the isolation level before
the transaction starts so you can not specify the isolation level
11
http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_
levels
6.1.
DATABASE ACCESS OBJECTS
231
directly when starting the transaction.
You have to call
\db\Transaction::setIsolationLevel()
yii
in this case after the
transaction has started.
Nesting Transactions
If your DBMS supports Savepoint, you may nest multiple transactions like
the following:
$db->transaction(function ($db) {
// outer transaction
});
$db->transaction(function ($db) {
// inner transaction
});
Or alternatively,
$outerTransaction = $db->beginTransaction();
try {
$db->createCommand($sql1)->execute();
$innerTransaction = $db->beginTransaction();
try {
$db->createCommand($sql2)->execute();
$innerTransaction->commit();
} catch (Exception $e) {
$innerTransaction->rollBack();
}
$outerTransaction->commit();
} catch (Exception $e) {
$outerTransaction->rollBack();
}
6.1.5 Replication and Read-Write Splitting
Many DBMS support database replication
12 to get better database avail-
ability 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 splitting, you can congure a
[
yii\db\Connection
component like the following:
'class' => 'yii\db\Connection',
12
http://en.wikipedia.org/wiki/Replication_(computing)#Database_
replication
232
CHAPTER 6.
WORKING WITH DATABASES
// 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
['dsn' => 'dsn for slave server
['dsn' => 'dsn for slave server
['dsn' => 'dsn for slave server
],
1'],
2'],
3'],
4'],
The above conguration species 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 conguration. 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
yii\db\Command::execute()
are considered as write queries, while all other queries done through
one of the query methods of
yii\db\Command are read queries.
You can get the currently active slave connection via
The
Connection
slaves.
$db->slave.
component supports load balancing and failover between
When performing a read query for the rst 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.
it will connect to the master.
If none of the slaves is available,
By conguring a
server status cache,
a
dead server can be remembered so that it will not be tried again during a
certain period of time.
6.1.
DATABASE ACCESS OBJECTS
233
Info: In the above conguration, a connection timeout of 10 seconds is specied 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 congure multiple masters with multiple slaves. For example,
[
'class' => 'yii\db\Connection',
// 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
['dsn' => 'dsn for slave server
['dsn' => 'dsn for slave server
['dsn' => 'dsn for slave server
],
1'],
2'],
3'],
4'],
The above conguration species two masters and four slaves. The
Connection
component also supports load balancing and failover between masters just
as it does between slaves. A dierence is that when none of the masters are
available an exception will be thrown.
Note: When you use the
masters
property to congure one or
multiple masters, all other properties for specifying a database
234
CHAPTER 6.
connection (e.g.
WORKING WITH DATABASES
dsn, username, password)
with the
Connection
ob-
ject itself will be ignored.
By default, transactions use the master connection. And within a transaction, 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.6 Working with Database Schema
Yii DAO provides a whole set of methods to let you manipulate database
schema, such as creating new tables, dropping a column from a table, etc.
These methods are listed as follows:
•
•
•
•
•
•
•
•
•
•
createTable(): creating a table
renameTable(): renaming a table
dropTable(): removing a table
truncateTable(): removing all rows in a table
addColumn(): adding a column
renameColumn(): renaming a column
dropColumn(): removing a column
alterColumn(): altering a column
addPrimaryKey(): adding a primary key
dropPrimaryKey(): removing a primary key
6.2.
QUERY BUILDER
•
•
•
•
235
addForeignKey(): adding a foreign key
dropForeignKey(): removing a foreign key
createIndex(): creating an index
dropIndex(): removing an index
These methods can be used like the following:
// CREATE TABLE
$db->createCommand()->createTable('post', [
'id' => 'pk',
'title' => 'string',
'text' => 'text',
]);
You can also retrieve the denition information about a table through the
getTableSchema()
method of a DB connection. For example,
$table = $db->getTableSchema('post');
The method returns a
yii\db\TableSchema object which contains the infor-
mation about the table's columns, primary keys, foreign keys, etc. All these
information are mainly utilized by query builder and active record to help
you write database-agnostic code.
6.2 Query Builder
Built on top of Database Access Objects, query builder allows you to construct a SQL statement in a programmatic and DBMS-agnostic way. Compared to writing raw SQLs, using query builder will help you write more
readable SQL-related code and generate more secure SQL statements.
Using query builder usually involves two steps:
1. Build a
FROM)
yii\db\Query
object to represent dierent parts (e.g.
SELECT,
of a SELECT SQL statement.
2. Execute a query method (e.g.
all())
of
yii\db\Query to retrieve data
from the database.
The following code shows a typical way of using query builder:
$rows = (new \yii\db\Query())
->select(['id', 'email'])
->from('user')
->where(['last_name' => 'Smith'])
->limit(10)
->all();
The above code generates and executes the following SQL statement, where
the
:last_name
parameter is bound with the string
'Smith'.
236
CHAPTER 6.
WORKING WITH DATABASES
SELECT `id`, `email`
FROM `user`
WHERE `last_name` = :last_name
LIMIT 10
Info: You usually mainly work with
\db\QueryBuilder.
yii\db\Query instead of yii
The latter is invoked by the former implic-
itly when you call one of the query methods.
yii\db\QueryBuilder
is the class responsible for generating DBMS-dependent SQL
statements (e.g.
quoting table/column names dierently) from
DBMS-independent
yii\db\Query
objects.
6.2.1 Building Queries
To build a
yii\db\Query
object, you call dierent query building methods
to specify dierent parts of a SQL statement. The names of these methods
resemble the SQL keywords used in the corresponding parts of the SQL
statement. For example, to specify the
would call the
from()
method.
FROM
part of a SQL statement, you
All the query building methods return the
query object itself, which allows you to chain multiple calls together.
In the following, we will describe the usage of each query building method.
select()
The
select()
method species the
SELECT
fragment of a SQL statement.
You can specify columns to be selected in either an array or a string, like the
following.
The column names being selected will be automatically quoted
when the SQL statement is being generated from a query object.
$query->select(['id', 'email']);
// equivalent to:
$query->select('id, email');
The column names being selected may include table prexes and/or column
aliases, like you do when writing raw SQLs. For example,
$query->select(['user.id AS user_id', 'email']);
// equivalent to:
$query->select('user.id AS user_id, email');
If you are using the array format to specify columns, you can also use the
array keys to specify the column aliases. For example, the above code can
be rewritten as follows,
$query->select(['user_id' => 'user.id', 'email']);
6.2.
QUERY BUILDER
If you do not call the
237
select()
selected, which means selecting
method when building a query,
all
*
will be
columns.
Besides column names, you can also select DB expressions.
You must
use the array format when selecting a DB expression that contains commas
to avoid incorrect automatic name quoting. For example,
$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email'])
;
Starting from version 2.0.1, you may also select sub-queries.
specify each sub-query in terms of a
yii\db\Query
You should
object. For example,
$subQuery = (new Query())->select('COUNT(*)')->from('user');
// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post`
$query = (new Query())->select(['id', 'count' => $subQuery])->from('post');
To select distinct rows, you may call
distinct(),
like the following:
// SELECT DISTINCT `user_id` ...
$query->select('user_id')->distinct();
You can call
addSelect()
to select additional columns. For example,
$query->select(['id', 'username'])
->addSelect(['email']);
from()
The
from()
method species the
FROM
fragment of a SQL statement.
For
example,
// SELECT * FROM `user`
$query->from('user');
You can specify the table(s) being selected from in either a string or an array.
The table names may contain schema prexes and/or table aliases, like you
do when writing raw SQLs. For example,
$query->from(['public.user u', 'public.post p']);
// equivalent to:
$query->from('public.user u, public.post p');
If you are using the array format, you can also use the array keys to specify
the table aliases, like the following:
$query->from(['u' => 'public.user', 'p' => 'public.post']);
Besides table names, you can also select from sub-queries by specifying them
in terms of
yii\db\Query
objects. For example,
$subQuery = (new Query())->select('id')->from('user')->where('status=1');
// SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u
$query->from(['u' => $subQuery]);
238
CHAPTER 6.
WORKING WITH DATABASES
WHERE
fragment of a SQL statement. You
where()
The
where()
method species the
can use one of the three formats to specify a
•
•
•
WHERE
condition:
'status=1'
['status' => 1, 'type' => 2]
e.g. ['like', 'name', 'test']
string format, e.g.,
hash format, e.g.
operator format,
String Format
String format is best used to specify very simple condi-
tions. It works as if you are writing a raw SQL. For example,
$query->where('status=1');
// or use parameter binding to bind dynamic parameter values
$query->where('status=:status', [':status' => $status]);
Do NOT embed variables directly in the condition like the following, especially if the variable values come from end user inputs, because this will make
your application subject to SQL injection attacks.
// Dangerous! Do NOT do this unless you are very certain $status must be an
integer.
$query->where("status=$status");
When using parameter binding, you may call
params()
or
addParams()
to
specify parameters separately.
$query->where('status=:status')
->addParams([':status' => $status]);
Hash Format
Hash format is best used to specify multiple
AND-concatenated
sub-conditions each being a simple equality assertion. It is written as an array whose keys are column names and values the corresponding values that
the columns should be. For example,
// ...WHERE (`status` = 10) AND (`type` IS NULL) AND (`id` IN (4, 8, 15))
$query->where([
'status' => 10,
'type' => null,
'id' => [4, 8, 15],
]);
As you can see, the query builder is intelligent enough to properly handle
values that are nulls or arrays.
You can also use sub-queries with hash format like the following:
$userQuery = (new Query())->select('id')->from('user');
// ...WHERE `id` IN (SELECT `id` FROM `user`)
$query->where(['id' => $userQuery]);
6.2.
QUERY BUILDER
Operator Format
239
Operator format allows you to specify arbitrary con-
ditions in a programmatic way. It takes the following format:
[operator, operand1, operand2, ...]
where the operands can each be specied in string format, hash format or
operator format recursively, while the operator can be one of the followings:
• and:
the operands should be concatenated together using
ample,
['and', 'id=1', 'id=2'] will generate id=1 AND id=2.
AND.
For ex-
If an operand
is an array, it will be converted into a string using the rules described
['and', 'type=1', ['or', 'id=1', 'id=2']] will gentype=1 AND (id=1 OR id=2). The method will NOT do any quoting
here. For example,
erate
or escaping.
• or:
similar to the
nated using
• between:
and
operator except that the operands are concate-
OR.
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
['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.
is in. For example,
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,
id IN (1, 2, 3).
['in', 'id', [1, 2, 3]]
will generate
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 repre-
senting 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
name LIKE '%tester%'. When the value range is given as an arLIKE 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
generate
ray, multiple
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
240
CHAPTER 6.
WORKING WITH DATABASES
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.
Note:
When using PostgreSQL you may also use
instead of
• or like:
nate the
• not like:
like
for case-insensitive matching.
similar to the
LIKE
ilike13
like
operator except that
OR
is used to concate-
predicates when operand 2 is an array.
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 yii\db
\Query representing the sub-query. It will build a EXISTS (sub-query)
similar to the
expression.
• not exists:
similar to the
-query) expression.
• >, <=, or any other valid
exists
operator and builds a
NOT EXISTS (sub
DB operator that takes two operands: the rst
operand must be a column name while the second operand a value.
For example,
['>', 'age', 10]
Appending Conditions
will generate
You can use
age>10.
andWhere()
or
orWhere()
to ap-
pend additional conditions to an existing one. You can call them multiple
times to append multiple conditions separately. For example,
$status = 10;
$search = 'yii';
$query->where(['status' => $status]);
if (!empty($search)) {
$query->andWhere(['like', 'title', $search]);
}
If
$search
is not empty, the following SQL statement will be generated:
... WHERE (`status` = 10) AND (`title` LIKE '%yii%')
Filter Conditions
When building
WHERE
conditions based on input from
end users, you usually want to ignore those empty input values. For example,
in a search form that allows you to search by username and email, you
would like to ignore the username/email condition if the user does not enter
anything in the username/email input eld.
using the
filterWhere()
You can achieve this goal by
method:
13
http://www.postgresql.org/docs/8.3/static/functions-matching.html#
FUNCTIONS-LIKE
6.2.
QUERY BUILDER
241
// $username and $email are from user inputs
$query->filterWhere([
'username' => $username,
'email' => $email,
]);
filterWhere() and where() is that the former
The only dierence between
will ignore empty values provided in the condition in hash format.
$email is empty while $username
...WHERE username=:username.
So if
is not, the above code will result in the SQL
Info: A value is considered empty if it is null, an empty array, an
empty string or a string consisting of whitespaces only.
Like
andWhere() and orWhere(), you can use andFilterWhere() and orFilterWhere()
to append additional lter conditions to the existing one.
orderBy()
The
orderBy()
method species the
ORDER BY
fragment of a SQL statement. For example,
`php
// . . . ORDER BY
id
ASC,
name
DESC
$query->orderBy([
`id' => SORT_ASC,
`name' => SORT_DESC,
]);
`
In the above code, the array keys are column names while the array values are
the corresponding order-by directions. The PHP constant
ascending sort and
If
ORDER BY
SORT_DESC
SORT_ASC
species
descending sort.
only involves simple column names, you can specify it using a
string, just like you do when writing raw SQLs. For example,
$query->orderBy('id ASC, name DESC');
Note: You should use the array format if
ORDER BY
involves some
DB expression.
You can call
addOrderBy()
to add additional columns to the
ment. For example,
$query->orderBy('id ASC')
->addOrderBy('name DESC');
ORDER BY
frag-
242
CHAPTER 6.
WORKING WITH DATABASES
groupBy()
The
groupBy()
method species the
GROUP BY
fragment of a SQL statement.
For example,
// ... GROUP BY `id`, `status`
$query->groupBy(['id', 'status']);
If
GROUP BY
only involves simple column names, you can specify it using a
string, just like you do when writing raw SQLs. For example,
$query->groupBy('id, status']);
Note: You should use the array format if
GROUP BY
involves some
DB expression.
You can call
addGroupBy()
to add additional columns to the
GROUP BY
frag-
ment. For example,
$query->groupBy(['id', 'status'])
->addGroupBy('age');
having()
The
having()
method species the
HAVING
fragment of a SQL statement. It
takes a condition which can be specied in the same way as that for where().
For example,
// ... HAVING `status` = 1
$query->having(['status' => 1]);
Please refer to the documentation for where() for more details about how to
specify a condition.
You can call
to the
HAVING
andHaving() or orHaving() to append additional conditions
fragment. For example,
// ... HAVING (`status` = 1) AND (`age` > 30)
$query->having(['status' => 1])
->andHaving(['>', 'age', 30]);
limit() and offset()
The
limit()
and
offset()
methods specify the
LIMIT
and
OFFSET
fragments
of a SQL statement. For example,
// ... LIMIT 10 OFFSET 20
$query->limit(10)->offset(20);
If you specify an invalid limit or oset (e.g.
a negative value), it will be
ignored.
Info:
For DBMS that do not support
LIMIT
and
OFFSET
(e.g.
MSSQL), query builder will generate a SQL statement that emulates the
LIMIT/OFFSET
behavior.
6.2.
QUERY BUILDER
243
join()
The
join()
method species the
JOIN
fragment of a SQL statement. For example,
`php
// . . . LEFT JOIN
post
ON
post.user_id
=
user.id
$query->join('LEFT JOIN', `post', `post.user_id = user.id');
`
The
-
join()
method takes four parameters:
$type: join type, e.g., 'INNER JOIN', 'LEFT JOIN'.
$table: the name of the table to be joined.
$on: optional, the join condition, i.e., the ON fragment.
Please refer to where() for details
about specifying a condition.
-
$params:
optional, the parameters to be bound to the join condition.
You can use the following shortcut methods to specify
and
RIGHT JOIN,
INNER JOIN, LEFT JOIN
respectively.
• innerJoin()
• leftJoin()
• rightJoin()
For example,
$query->leftJoin('post', 'post.user_id = user.id');
To join with multiple tables, call the above join methods multiple times,
once for each table.
Besides joining with tables, you can also join with sub-queries. To do so,
specify the sub-queries to be joined as
yii\db\Query
objects. For example,
$subQuery = (new \yii\db\Query())->from('post');
$query->leftJoin(['u' => $subQuery], 'u.id = author_id');
In this case, you should put the sub-query in an array and use the array key
to specify the alias.
union()
The
union()
method species the
UNION
fragment of a SQL statement. For
example,
$query1 = (new \yii\db\Query())
->select("id, category_id AS type, name")
->from('post')
->limit(10);
$query2 = (new \yii\db\Query())
->select('id, type, name')
->from('user')
->limit(10);
$query1->union($query2);
You can call
union()
multiple times to append more
UNION
fragments.
244
CHAPTER 6.
WORKING WITH DATABASES
6.2.2 Query Methods
yii\db\Query provides a whole set of methods for dierent query purposes:
• all(): returns an array of rows with each row being an associative
array of name-value pairs.
• one(): returns the rst row of the result.
• column(): returns the rst column of the result.
• scalar(): returns a scalar value located at the
rst row and rst
column of the result.
• exists():
returns a value indicating whether the query contains any
result.
• count(): returns the result of a COUNT query.
• Other aggregation query methods, including sum($q), average($q),
max($q), min($q). The $q parameter is mandatory for these methods
and can be either a column name or a DB expression.
For example,
// SELECT `id`, `email` FROM `user`
$rows = (new \yii\db\Query())
->select(['id', 'email'])
->from('user')
->all();
// SELECT * FROM `user` WHERE `username` LIKE `%test%`
$row = (new \yii\db\Query())
->from('user')
->where(['like', 'username', 'test'])
->one();
Note: The
one()
method only returns the rst row of the query
result. It does NOT add
LIMIT 1 to the generated SQL statement.
This is ne and preferred if you know the query will return only
one or a few rows of data (e.g.
if you are querying with some
primary keys). However, if the query may potentially result in
limit(1) explicitly to improve
(new \yii\db\Query())->from('user')->limit
many rows of data, you should call
the performance, e.g.,
(1)->one().
All these query methods take an optional
connection
$db
parameter representing the
DB
that should be used to perform a DB query. If you omit this
parameter, the
db
application component will be used as the DB connection.
Below is another example using the
count()
query method:
// executes SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name
$count = (new \yii\db\Query())
->from('user')
->where(['last_name' => 'Smith'])
->count();
6.2.
QUERY BUILDER
245
When you call a query method of
yii\db\Query, it actually does the follow-
ing work internally:
yii\db\QueryBuilder to generate a SQL statement based on the
current construct of yii\db\Query;
• Create a yii\db\Command object with the generated SQL statement;
• Call a query method (e.g. queryAll()) of yii\db\Command to execute
•
Call
the SQL statement and retrieve the data.
Sometimes, you may want to examine or use the SQL statement built from
a
yii\db\Query
object. You can achieve this goal with the following code:
$command = (new \yii\db\Query())
->select(['id', 'email'])
->from('user')
->where(['last_name' => 'Smith'])
->limit(10)
->createCommand();
// show the SQL statement
echo $command->sql;
// show the parameters to be bound
print_r($command->params);
// returns all rows of the query result
$rows = $command->queryAll();
Indexing Query Results
When you call
all(),
it will return an array of rows which are indexed
by consecutive integers. Sometimes you may want to index them dierently,
such as indexing by a particular column or expression values. You can achieve
this goal by calling
indexBy()
before
all().
For example,
// returns [100 => ['id' => 100, 'username' => '...', ...], 101 => [...],
103 => [...], ...]
$query = (new \yii\db\Query())
->from('user')
->limit(10)
->indexBy('id')
->all();
To index by expression values, pass an anonymous function to the
indexBy()
method:
$query = (new \yii\db\Query())
->from('user')
->indexBy(function ($row) {
return $row['id'] . $row['username'];
})->all();
The anonymous function takes a parameter
$row
which contains the current
row data and should return a scalar value which will be used as the index
value for the current row.
246
CHAPTER 6.
WORKING WITH DATABASES
Batch Query
yii\db\Query::
all() are not suitable because they require loading all data into the memory.
When working with large amounts of data, methods such as
To keep the memory requirement low, Yii provides the so-called batch query
support. A batch query makes uses of the data cursor and fetches data in
batches.
Batch query can be used like the following:
use yii\db\Query;
$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
}
yii\db\Query::batch() and yii\db\Query::each() return an
yii\db\BatchQueryResult object which implements the Iterator interface
The method
and thus can be used in the
foreach
construct. During the rst iteration, a
SQL query is made to the database. Data are then fetched in batches in the
remaining 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 rst parameter to the
Compared to the
batch()
or
each()
method.
yii\db\Query::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 reduce memory usage.
If you specify the query result to be indexed by some column via
\Query::indexBy(),
yii\db
the batch query will still keep the proper index. For
example,
$query = (new \yii\db\Query())
->from('user')
->indexBy('username');
foreach ($query->batch() as $users) {
// $users is indexed by the "username" column
}
foreach ($query->each() as $username => $user) {
}
6.3.
ACTIVE RECORD
247
6.3 Active Record
Active Record
14 provides an object-oriented interface for accessing and ma-
nipulating data stored in databases.
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 particular column in that row. Instead of writing raw SQL statements,
you would access Active Record attributes and call Active Record methods
to access and manipulate the data stored in database tables.
Customer is an Active Record class which is associname is a column of the customer table. You
code to insert a new row into the customer table:
For example, assume
ated with the
customer
table and
can write the following
$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();
The above code is equivalent to using the following raw SQL statement
for MySQL, which is less intuitive, more error prone, and may even have
compatibility problems if you are using a dierent kind of database:
$db->createCommand('INSERT INTO `customer` (`name`) VALUES (:name)', [
':name' => 'Qiang',
])->execute();
Yii provides the Active Record support for the following relational databases:
•
•
•
•
•
•
yii\db\ActiveRecord
yii\db\ActiveRecord
SQLite 2 and 3: via yii\db\ActiveRecord
Microsoft SQL Server 2008 or later: via yii\db\ActiveRecord
Oracle: via yii\db\ActiveRecord
CUBRID 9.3 or later: via yii\db\ActiveRecord (Note that due
MySQL 4.1 or later: via
PostgreSQL 7.3 or later: via
bug
to a
15 in the cubrid PDO extension, quoting of values will not work,
so you need CUBRID 9.3 as the client as well as the server)
•
Sphinx: via
yii\sphinx\ActiveRecord,
requires the
yii2-sphinx
ex-
tension
•
ElasticSearch:
via
yii2-elasticsearch
yii\elasticsearch\ActiveRecord,
requires the
extension
Additionally, Yii also supports using Active Record with the following NoSQL
databases:
•
Redis 2.6.12 or later: via
-redis
•
MongoDB 1.3.0 or later: via
yii2-mongodb
14
15
yii\redis\ActiveRecord,
requires the
yii2
extension
yii\mongodb\ActiveRecord, requires the
extension
http://en.wikipedia.org/wiki/Active_record_pattern
http://jira.cubrid.org/browse/APIS-658
248
CHAPTER 6.
WORKING WITH 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 get started, declare an Active Record class by extending
yii\db\ActiveRecord.
Because each Active Record class is associated with a database table, in this
class you should override the
tableName()
method to specify which table
the class is associated with.
In the following example, we declare an Active Record class named
Customer
for the
customer
database table.
namespace app\models;
use yii\db\ActiveRecord;
class Customer extends ActiveRecord
{
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
/**
* @return string the name of the table associated with this
ActiveRecord class.
*/
public static function tableName()
{
return 'customer';
}
}
Active Record instances are considered as models. For this reason, we usually put Active Record classes under the
app\models
namespace (or other
namespaces for keeping model classes).
Because
its
all
yii\db\ActiveRecord
extends from
yii\base\Model,
it inher-
model features, such as attributes, validation rules, data serialization,
etc.
6.3.2 Connecting to Databases
By default, Active Record uses the
connection
db
application component as the
DB
to access and manipulate the database data. As explained in
Database Access Objects, you can congure the
cation conguration like shown below,
return [
'components' => [
'db' => [
'class' => 'yii\db\Connection',
db
component in the appli-
6.3.
];
ACTIVE RECORD
],
],
249
'dsn' => 'mysql:host=localhost;dbname=testdb',
'username' => 'demo',
'password' => 'demo',
If you want to use a dierent database connection other than the
nent, you should override the
getDb()
db
compo-
method:
class Customer extends ActiveRecord
{
// ...
}
public static function getDb()
{
// use the "db2" application component
return \Yii::$app->db2;
}
6.3.3 Querying Data
After declaring an Active Record class, you can use it to query data from
the corresponding database table. The process usually takes the following
three steps:
1. Create a new query object by calling the
find()
yii\db\ActiveRecord::
method;
2. Build the query object by calling query building methods;
3. Call a query method to retrieve data in terms of Active Record instances.
As you can see, this is very similar to the procedure with query builder.
The only dierence is that instead of using the
new
operator to create a
yii\db\ActiveRecord::find() to return a new query
class yii\db\ActiveQuery.
query object, you call
object which is of
Below are some examples showing how to use Active Query to query
data:
// return a single customer whose ID is 123
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::find()
->where(['id' => 123])
->one();
// return all active customers and order them by their IDs
// SELECT * FROM `customer` WHERE `status` = 1 ORDER BY `id`
$customers = Customer::find()
250
CHAPTER 6.
WORKING WITH DATABASES
->where(['status' => Customer::STATUS_ACTIVE])
->orderBy('id')
->all();
// return the number of active customers
// SELECT COUNT(*) FROM `customer` WHERE `status` = 1
$count = Customer::find()
->where(['status' => Customer::STATUS_ACTIVE])
->count();
// return all active customers in an array indexed by customer IDs
// SELECT * FROM `customer`
$customers = Customer::find()
->indexBy('id')
->all();
In the above,
Customer
customer
$customer
is a
Customer
object while
$customers
is an array of
objects. They are all populated with the data retrieved from the
table.
Info: Because
you can use
yii\db\ActiveQuery extends from yii\db\Query,
all
query building methods and query methods as
described in the Section Query Builder.
Because it is a common task to query by primary key values or a set of
column values, Yii provides two shortcut methods for this purpose:
• yii\db\ActiveRecord::findOne():
returns a single Active Record
instance populated with the rst row of the query result.
• yii\db\ActiveRecord::findAll():
instances populated with
all
returns an array of Active Record
query result.
Both methods can take one of the following parameter formats:
•
a scalar value: the value is treated as the desired primary key value to
be looked for. Yii will determine automatically which column is the
primary key column by reading database schema information.
•
an array of scalar values: the array is treated as the desired primary
key values to be looked for.
•
an associative array: the keys are column names and the values are
the corresponding desired column values to be looked for. Please refer
to Hash Format for more details.
The following code shows how theses methods can be used:
// returns a single customer whose ID is 123
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// returns customers whose ID is 100, 101, 123 or 124
// SELECT * FROM `customer` WHERE `id` IN (100, 101, 123, 124)
$customers = Customer::findAll([100, 101, 123, 124]);
// returns an active customer whose ID is 123
6.3.
ACTIVE RECORD
251
// SELECT * FROM `customer` WHERE `id` = 123 AND `status` = 1
$customer = Customer::findOne([
'id' => 123,
'status' => Customer::STATUS_ACTIVE,
]);
// returns all inactive customers
// SELECT * FROM `customer` WHERE `status` = 0
$customer = Customer::findAll([
'status' => Customer::STATUS_INACTIVE,
]);
yii\db\ActiveRecord::findOne() nor yii\db
\ActiveQuery::one() will add LIMIT 1 to the generated SQL
Note:
Neither
statement.
If your query may return many rows of data, you
limit(1) explicitly to improve
Customer::find()->limit(1)->one().
should call
the performance, e.g.,
Besides using query building methods, you can also write raw SQLs to query
data and populate the results into Active Record objects. You can do so by
calling the
yii\db\ActiveRecord::findBySql()
method:
// returns all inactive customers
$sql = 'SELECT * FROM customer WHERE status=:status';
$customers = Customer::findBySql($sql, [':status' => Customer::
STATUS_INACTIVE])->all();
Do not call extra query building methods after calling
findBySql()
as they
will be ignored.
6.3.4 Accessing Data
As aforementioned, the data brought back from the database are populated
into Active Record instances, and each row of the query result corresponds
to a single Active Record instance.
You can access the column values by
accessing the attributes of the Active Record instances, for example,
// "id" and "email" are the names of columns in the "customer" table
$customer = Customer::findOne(123);
$id = $customer->id;
$email = $customer->email;
Note: The Active Record attributes are named after the associated table columns in a case-sensitive manner. Yii automatically
denes an attribute in Active Record for every column of the associated table. You should NOT redeclare any of the attributes.
Because Active Record attributes are named after table columns, you may
nd you are writing PHP code like
$customer->first_name,
which uses under-
scores to separate words in attribute names if your table columns are named
252
CHAPTER 6.
WORKING WITH DATABASES
in this way. If you are concerned about code style consistency, you should
rename your table columns accordingly (to use camelCase, for example.)
Data Transformation
It often happens that the data being entered and/or displayed are in a dierent format from the one used in storing the data in a database. For example,
in the database you are storing customers' birthdays as UNIX timestamps
(which is not a good design, though), while in most cases you would like to
'YYYY/MM/DD'. To achieve
methods in the Customer Active
manipulate birthdays as strings in the format of
this goal, you can dene data transformation
Record class like the following:
class Customer extends ActiveRecord
{
// ...
public function getBirthdayText()
{
return date('Y/m/d', $this->birthday);
}
}
public function setBirthdayText($value)
{
$this->birthday = strtotime($value);
}
Now in your PHP code, instead of accessing
access
$customer->birthdayText,
$customer->birthday,
you would
which will allow you to input and display
customer birthdays in the format of
'YYYY/MM/DD'.
Tip: the above shows an easy approach to achieve data transformation in general. For date values Yii provides a better way using
the DateValidator and a DatePicker widget, which is described
in the JUI Widgets section.
Retrieving Data in Arrays
While retrieving data in terms of Active Record objects is convenient and
exible, it is not always desirable when you have to bring back a large amount
of data due to the big memory footprint. In this case, you can retrieve data
using PHP arrays by calling
asArray()
before executing a query method:
// return all customers
// each customer is returned as an associative array
$customers = Customer::find()
->asArray()
->all();
6.3.
ACTIVE RECORD
Note:
253
While this method saves memory and improves perfor-
mance, it is closer to the lower DB abstraction layer and you
will lose most of the Active Record features. A very important
distinction lies in the data type of the column values. When you
return data in Active Record instances, column values will be
automatically typecast according to the actual column types; on
the other hand when you return data in arrays, column values
will be strings (since they are the result of PDO without any
processing), regardless their actual column types.
Retrieving Data in Batches
In Query Builder, we have explained that you may use
batch query
to min-
imize your memory usage when querying a large amount of data from the
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) {
// $customer is a Customer object
}
6.3.5 Saving Data
Using Active Record, you can easily save data to database by taking the
following steps:
1. Prepare an Active Record instance
2. Assign new values to Active Record attributes
3. Call
yii\db\ActiveRecord::save()
For example,
// insert a new row of data
$customer = new Customer();
$customer->name = 'James';
$customer->email = '
[email protected]';
$customer->save();
to save the data into database.
254
CHAPTER 6.
WORKING WITH DATABASES
// update an existing row of data
$customer = Customer::findOne(123);
$customer->email = '
[email protected]';
$customer->save();
The
save()
method can either insert or update a row of data, depending
on the state of the Active Record instance. If the instance is newly created
via the
new
operator, calling
save()
will cause insertion of a new row; If the
save()
instance is the result of a query method, calling
will update the row
associated with the instance.
You can dierentiate the two states of an Active Record instance by
checking its
save()
isNewRecord
property value.
This property is also used by
internally as follows:
public function save($runValidation = true, $attributeNames = null)
{
if ($this->getIsNewRecord()) {
return $this->insert($runValidation, $attributeNames);
} else {
return $this->update($runValidation, $attributeNames) !== false;
}
}
Tip:
You can call
insert()
or
update()
directly to insert or
update a row.
Data Validation
Because
yii\db\ActiveRecord extends from yii\base\Model, it shares the
same data validation feature. You can declare validation rules by overriding
the
rules() method and perform data validation by calling the validate()
method.
When you call
save(),
by default it will call
validate()
automatically.
Only when the validation passes, will it actually save the data; otherwise it
will simply return false, and you can check the
errors
property to retrieve
the validation error messages.
Tip: If you are certain that your data do not need validation (e.g.,
the data comes from trustable sources), you can call
save(false)
to skip the validation.
Massive Assignment
Like normal models, Active Record instances also enjoy the massive assignment feature. Using this feature, you can assign values to multiple attributes
of an Active Record instance in a single PHP statement, like shown below.
Do remember that only safe attributes can be massively assigned, though.
6.3.
ACTIVE RECORD
255
$values = [
'name' => 'James',
'email' => '
[email protected]',
];
$customer = new Customer();
$customer->attributes = $values;
$customer->save();
Updating Counters
It is a common task to increment or decrement a column in a database table.
We call such columns as counter columns. You can use
updateCounters()
to update one or multiple counter columns. For example,
$post = Post::findOne(100);
// UPDATE `post` SET `view_count` = `view_count` + 1 WHERE `id` = 100
$post->updateCounters(['view_count' => 1]);
Note:
If you use
yii\db\ActiveRecord::save()
to update a
counter column, you may end up with inaccurate result, because
it is likely the same counter is being saved by multiple requests
which read and write the same counter value.
Dirty Attributes
When you call
tributes
save()
to save an Active Record instance, only
are being saved.
An attribute is considered
dirty
dirty at-
if its value has
been modied since it was loaded from DB or saved to DB most recently.
Note that data validation will be performed regardless if the Active Record
instance has dirty attributes or not.
Active Record automatically maintains the list of dirty attributes.
It
does so by maintaining an older version of the attribute values and comparing them with the latest one.
You can call
yii\db\ActiveRecord::
getDirtyAttributes() to get the attributes that are currently dirty. You
can also call yii\db\ActiveRecord::markAttributeDirty() to explicitly
mark an attribute as dirty.
If you are interested in the attribute values prior to their most recent
modication, you may call
getOldAttributes()
or
getOldAttribute().
Default Attribute Values
Some of your table columns may have default values dened in the database.
Sometimes, you may want to pre-populate your Web form for an Active
Record instance with these default values. To avoid writing the same default
256
CHAPTER 6.
values again, you can call
WORKING WITH DATABASES
loadDefaultValues() to populate the DB-dened
default values into the corresponding Active Record attributes:
$customer = new Customer();
$customer->loadDefaultValues();
// $customer->xyz will be assigned the default value declared when defining
the "xyz" column
Updating Multiple Rows
The methods described above all work on individual Active Record instances,
causing inserting or updating of individual table rows. To update multiple
rows simultaneously, you should call
updateAll(), instead, which is a static
method.
// UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com`
Customer::updateAll(['status' => Customer::STATUS_ACTIVE], ['like', 'email',
'@example.com']);
Similarly, you can call
updateAllCounters()
to update counter columns of
multiple rows at the same time.
// UPDATE `customer` SET `age` = `age` + 1
Customer::updateAllCounters(['age' => 1]);
6.3.6 Deleting Data
To delete a single row of data, rst retrieve the Active Record instance corresponding to that row and then call the
yii\db\ActiveRecord::delete()
method.
$customer = Customer::findOne(123);
$customer->delete();
You can call
yii\db\ActiveRecord::deleteAll()
to delete multiple or all
rows of data. For example,
Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]);
Note: Be very careful when calling
deleteAll()
because it may
totally erase all data from your table if you make a mistake in
specifying the condition.
6.3.7 Active Record Life Cycles
It is important to understand the life cycles of Active Record when it is used
for dierent purposes. During each life cycle, a certain sequence of methods
will be invoked, and you can override these methods to get a chance to
customize the life cycle.
You can also respond to certain Active Record
events triggered during a life cycle to inject your custom code. These events
6.3.
ACTIVE RECORD
257
are especially useful when you are developing Active Record behaviors which
need to customize Active Record life cycles.
In the following, we will summarize various Active Record life cycles and
the methods/events that are involved in the life cycles.
New Instance Life Cycle
When creating a new Active Record instance via the
new
operator, the fol-
lowing life cycle will happen:
1. class constructor;
2.
init():
triggers an
EVENT_INIT
event.
Querying Data Life Cycle
When querying data through one of the querying methods, each newly populated Active Record will undergo the following life cycle:
1. class constructor.
2.
init():
3.
afterFind():
triggers an
EVENT_INIT
triggers an
event.
EVENT_AFTER_FIND
event.
Saving Data Life Cycle
When calling
save()
to insert or update an Active Record instance, the
following life cycle will happen:
EVENT_BEFORE_VALIDATE event. If the method returns false or yii
\base\ModelEvent::$isValid is false, the rest of the steps will be
1. an
skipped.
2. Performs data validation. If data validation fails, the steps after Step
3 will be skipped.
3. an
EVENT_AFTER_VALIDATE
event.
EVENT_BEFORE_INSERT or EVENT_BEFORE_UPDATE event. If the method
returns false or yii\base\ModelEvent::$isValid is false, the rest of
4. an
the steps will be skipped.
5. Performs the actual data insertion or updating;
6. an
EVENT_AFTER_INSERT
or
EVENT_AFTER_UPDATE
event.
258
CHAPTER 6.
WORKING WITH DATABASES
Deleting Data Life Cycle
When calling
delete()
to delete an Active Record instance, the following
life cycle will happen:
EVENT_BEFORE_DELETE event.
\base\ModelEvent::$isValid is
1. an
If the method returns false or
yii
false, the rest of the steps will be
skipped.
2. perform the actual data deletion
3. an
EVENT_AFTER_DELETE
event.
Note: Calling any of the following methods will NOT initiate any
of the above life cycles:
•
•
•
•
yii\db\ActiveRecord::updateAll()
yii\db\ActiveRecord::deleteAll()
yii\db\ActiveRecord::updateCounters()
yii\db\ActiveRecord::updateAllCounters()
6.3.8 Working with Transactions
There are two ways of using transactions while working with Active Record.
The rst way is to explicitly enclose Active Record method calls in a
transactional block, like shown below,
$customer = Customer::findOne(123);
Customer::getDb()->transaction(function($db) use ($customer) {
$customer->id = 200;
$customer->save();
// ...other DB operations...
});
// or alternatively
$transaction = Customer::getDb()->beginTransaction();
try {
$customer->id = 200;
$customer->save();
// ...other DB operations...
$transaction->commit();
} catch(\Exception $e) {
$transaction->rollBack();
throw $e;
}
The second way is to list the DB operations that require transactional support in the
yii\db\ActiveRecord::transactions() method.
For example,
6.3.
ACTIVE RECORD
259
class Customer extends ActiveRecord
{
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,
];
}
}
The
yii\db\ActiveRecord::transactions()
method should return an ar-
ray whose keys are scenario names and values the corresponding operations
that should be enclosed within transactions. You should use the following
constants to refer to dierent DB operations:
• OP_INSERT:
• OP_UPDATE:
• OP_DELETE:
Use
|
insert();
update operation performed by update();
deletion operation performed by delete().
insertion operation performed by
operators to concatenate the above constants to indicate multiple
operations. You may also use the shortcut constant
OP_ALL
to refer to all
three operations above.
6.3.9 Optimistic Locks
Optimistic locking is a way to prevent conicts that may occur when a single
row of data is being updated by multiple users. For example, both user A
and user B are editing the same wiki article at the same time. After user A
saves his edits, user B clicks on the Save button in an attempt to save his
edits as well. Because user B was actually working on an outdated version of
the article, it would be desirable to have a way to prevent him from saving
the article and show him some hint message.
Optimistic locking solves the above problem by using a column to record
the version number of each row. When a row is being saved with an outdated version number, a
yii\db\StaleObjectException
exception will be
thrown, which prevents the row from being saved. Optimistic locking is only
yii\db
\ActiveRecord::update() or yii\db\ActiveRecord::delete(), respectively.
supported when you update or delete an existing row of data using
To use optimistic locking,
1. Create a column in the DB table associated with the Active Record
class to store the version number of each row. The column should be
of big integer type (in MySQL it would be
2. Override the
BIGINT DEFAULT 0).
yii\db\ActiveRecord::optimisticLock()
return the name of this column.
method to
260
CHAPTER 6.
WORKING WITH DATABASES
3. In the Web form that takes user inputs, add a hidden eld to store
the current version number of the row being updated.
Be sure your
version attribute has input validation rules and validates successfully.
4. In the controller action that updates the row using Active Record, try
and catch the
yii\db\StaleObjectException
exception. Implement
necessary business logic (e.g. merging the changes, prompting staled
data) to resolve the conict.
For example, assume the version column is named as
version.
You can im-
plement optimistic locking with the code like the following.
// ------ view code ------use yii\helpers\Html;
// ...other input fields
echo Html::activeHiddenInput($model, 'version');
// ------ controller code ------use yii\db\StaleObjectException;
public function actionUpdate($id)
{
$model = $this->findModel($id);
}
try {
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', <?= $urlParams ?>]);
} else {
return $this->render('update', [
'model' => $model,
]);
}
} catch (StaleObjectException $e) {
// logic to resolve the conflict
}
6.3.10 Working with Relational Data
Besides working with individual database tables, Active Record is also capable of bringing together related data, making them readily accessible through
the primary data. For example, the customer data is related with the order
data because one customer may have placed one or multiple orders. With
appropriate declaration of this relation, you may be able to access a customer's order information using the expression
$customer->orders which gives
Order Active
back the customer's order information in terms of an array of
Record instances.
6.3.
ACTIVE RECORD
261
Declaring Relations
To work with relational data using Active Record, you rst need to declare
relations in Active Record classes.
relation method
The task is as simple as declaring a
for every interested relation, like the following,
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'])
;
}
}
In the above code, we have declared an
and a
customer
relation for the
Order
orders
Each relation method must be named as
letter is in lower case) the
sensitive.
relation for the
Customer
class,
class.
relation name.
getXyz.
We call
xyz
(the rst
Note that relation names are
case
While declaring a relation, you should specify the following information:
•
the multiplicity of the relation: specied by calling either
or
hasOne().
hasMany()
In the above example you may easily read in the relation
declarations that a customer has many orders while an order only has
one customer.
•
the name of the related Active Record class: specied as the rst parameter to either
is to call
hasMany()
Xyz::className()
or
hasOne().
A recommended practice
to get the class name string so that you
can receive IDE auto-completion support as well as error detection at
compiling stage.
•
the link between the two types of data: species the column(s) through
which the two types of data are related.
The array values are the
columns of the primary data (represented by the Active Record class
that you are declaring relations), while the array keys are the columns
of the related data.
Accessing Relational Data
After declaring relations, you can access relational data through relation
names. This is just like accessing an object property dened by the relation
method. For this reason, we call it
relation property.
For example,
262
CHAPTER 6.
WORKING WITH DATABASES
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
// $orders is an array of Order objects
$orders = $customer->orders;
Info: When you declare a relation named
getXyz(),
you will be able to access
xyz
xyz
via a getter method
like an object property.
Note that the name is case sensitive.
If a relation is declared with
hasMany(), accessing this relation property will
return an array of the related Active Record instances; if a relation is declared
with
hasOne(), accessing the relation property will return the related Active
Record instance or null if no related data is found.
When you access a relation property for the rst time, a SQL statement
will be executed, like shown in the above example. If the same property is
accessed again, the previous result will be returned without re-executing the
SQL statement. To force re-executing the SQL statement, you should unset
the relation property rst:
unset($customer->orders).
Dynamic Relational Query
Because a relation method returns an instance of
yii\db\ActiveQuery, you
can further build this query using query building methods before performing
DB query. For example,
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `subtotal` > 200 ORDER BY `id`
$orders = $customer->getOrders()
->where(['>', 'subtotal', 200])
->orderBy('id')
->all();
Sometimes you may even want to parameterize a relation declaration so that
you can more easily perform dynamic relational query. For example, you may
declare a
bigOrders
relation as follows,
class Customer extends ActiveRecord
{
public function getBigOrders($threshold = 100)
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])
->where('subtotal > :threshold', [':threshold' => $threshold])
->orderBy('id');
}
}
Then you will be able to perform the following relational queries:
6.3.
ACTIVE RECORD
263
// SELECT * FROM `order` WHERE `subtotal` > 200 ORDER BY `id`
$orders = $customer->getBigOrders(200)->all();
// SELECT * FROM `order` WHERE `subtotal` > 100 ORDER BY `id`
$orders = $customer->bigOrders;
Note: While a relation method returns a
yii\db\ActiveQuery
yii
instance, accessing a relation property will either return a
\db\ActiveRecord instance or an array of that.
This is dierent
from a normal object property whose property value is of the
same type as the dening getter method.
Unlike accessing a relation property, each time you perform a dynamic relational query via a relation method, a SQL statement will be executed, even
if the same dynamic relational query is performed before.
Relations via a Junction Table
In database modelling, when the multiplicity between two related tables is
16 is usually introduced. For example, the
many-to-many, a junction table
order table
order_item.
and the
item
table may be related via a junction table named
One order will then correspond to multiple order items, while
one product item will also correspond to multiple order items.
via() or viaTable()
The dierence between via() and viaTable()
When declaring such relations, you would call either
to specify the junction table.
is that the former species the junction table in terms of an existing relation
name while the latter directly the junction table. For example,
class Order extends ActiveRecord
{
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->viaTable('order_item', ['order_id' => 'id']);
}
}
or alternatively,
class Order extends ActiveRecord
{
public function getOrderItems()
{
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
}
public function getItems()
{
16
https://en.wikipedia.org/wiki/Junction_table
264
}
CHAPTER 6.
}
WORKING WITH DATABASES
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItems');
The usage of relations declared with a junction table is the same as that of
normal relations. For example,
// SELECT * FROM `order` WHERE `id` = 100
$order = Order::findOne(100);
// SELECT * FROM `order_item` WHERE `order_id` = 100
// SELECT * FROM `item` WHERE `item_id` IN (...)
// returns an array of Item objects
$items = $order->items;
Lazy Loading and Eager Loading
In Accessing Relational Data, we explained that you can access a relation
property of an Active Record instance like accessing a normal object property. A SQL statement will be executed only when you access the relation
property the rst time. We call such relational data accessing method
loading.
lazy
For example,
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
$orders = $customer->orders;
// no SQL executed
$orders2 = $customer->orders;
Lazy loading is very convenient to use. However, it may suer from a performance issue when you need to access the same relation property of multiple
Active Record instances. Consider the following code example. How many
SQL statements will be executed?
// SELECT * FROM `customer` LIMIT 100
$customers = Customer::find()->limit(100)->all();
foreach ($customers as $customer) {
// SELECT * FROM `order` WHERE `customer_id` = ...
$orders = $customer->orders;
}
As you can see from the code comment above, there are 101 SQL statements
being executed!
This is because each time you access the
property of a dierent
Customer
orders
be executed.
To solve this performance problem, you can use the so-called
approach as shown below,
relation
object in the for-loop, a SQL statement will
eager loading
6.3.
ACTIVE RECORD
265
// SELECT * FROM `customer` LIMIT 100;
// SELECT * FROM `orders` WHERE `customer_id` IN (...)
$customers = Customer::find()
->with('orders')
->limit(100)
->all();
foreach ($customers as $customer) {
// no SQL executed
$orders = $customer->orders;
}
By calling
yii\db\ActiveQuery::with(),
you instruct Active Record to
bring back the orders for the rst 100 customers in one single SQL statement.
As a result, you reduce the number of the executed SQL statements from
101 to 2!
You can eagerly load one or multiple relations.
load
nested relations.
You can even eagerly
A nested relation is a relation that is declared within
Customer is related with Order
Order is related with Item through the items
relation. When querying for Customer, you can eagerly load items using the
nested relation notation orders.items.
a related Active Record class.
through the
orders
For example,
relation, and
The following code shows dierent usage of
Customer
class has two relations
one relation
orders
and
with().
country,
We assume the
while the
Order
class has
items.
// eager loading both "orders" and "country"
$customers = Customer::find()->with('orders', 'country')->all();
// equivalent to the array syntax below
$customers = Customer::find()->with(['orders', 'country'])->all();
// no SQL executed
$orders= $customers[0]->orders;
// no SQL executed
$country = $customers[0]->country;
// eager loading "orders" and the nested relation "orders.items"
$customers = Customer::find()->with('orders.items')->all();
// access the items of the first order of the first customer
// no SQL executed
$items = $customers[0]->orders[0]->items;
You can eagerly load deeply nested relations, such as
relations will be eagerly loaded. That is, when you call
d,
you will be eagerly load
a, a.b, a.b.c
and
Info: In general, when eagerly loading
a.b.c.d.
with()
All parent
using
N
M
N+M
relations among which
relations are dened with a junction table, a total number of
+1 SQL statements will be executed.
a.b.c.d counts as 4 relations.
a.b.c.
a.b.c.d.
Note that a nested relation
266
CHAPTER 6.
WORKING WITH DATABASES
When eagerly loading a relation, you can customize the corresponding relational query using an anonymous function. For example,
// find customers and bring back together their country and active orders
// SELECT * FROM `customer`
// SELECT * FROM `country` WHERE `id` IN (...)
// SELECT * FROM `order` WHERE `customer_id` IN (...) AND `status` = 1
$customers = Customer::find()->with([
'country',
'orders' => function ($query) {
$query->andWhere(['status' => Order::STATUS_ACTIVE]);
},
])->all();
When customizing the relational query for a relation, you should specify
the relation name as an array key and use an anonymous function as the
corresponding array value.
The anonymous function will receive a
parameter which represents the
$query
yii\db\ActiveQuery object used to perform
the relational query for the relation.
In the code example above, we are
modifying the relational query by appending an additional condition about
order status.
Note: If you call
select()
while eagerly loading relations, you
have to make sure the columns referenced in the relation declarations are being selected. Otherwise, the related models may not
be loaded properly. 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();
Joining with Relations
Note: The content described in this subsection is only applicable
to relational databases, such as MySQL, PostgreSQL, etc.
The relational queries that we have described so far only reference the primary table columns when querying for the primary data. In reality we often
need to reference columns in the related tables. For example, we may want
to bring back the customers who have at least one active order.
this problem, we can build a join query like the following:
// SELECT `customer`.* FROM `customer`
// LEFT JOIN `order` ON `order`.`customer_id` = `customer`.`id`
// WHERE `order`.`status` = 1
//
// SELECT * FROM `order` WHERE `customer_id` IN (...)
$customers = Customer::find()
To solve
6.3.
ACTIVE RECORD
267
->select('customer.*')
->leftJoin('order', '`order`.`customer_id` = `customer`.`id`')
->where(['order.status' => Order::STATUS_ACTIVE])
->with('orders')
->all();
Note: It is important to disambiguate column names when building relational queries involving JOIN SQL statements. A common practice is to prex column names with their corresponding
table names.
However, a better approach is to exploit the existing relation declarations
by calling
yii\db\ActiveQuery::joinWith():
$customers = Customer::find()
->joinWith('orders')
->where(['order.status' => Order::STATUS_ACTIVE])
->all();
Both approaches execute the same set of SQL statements.
The latter ap-
proach is much cleaner and drier, though.
By default,
joinWith() will use LEFT JOIN to join the primary table with
the related table. You can specify a dierent join type (e.g.
its third parameter
$joinType.
If the join type you want is
innerJoinWith(), instead.
joinWith() will eagerly load the
RIGHT JOIN) via
INNER JOIN, you
can simply call
Calling
related data by default. If you
do not want to bring in the related data, you can specify its second parameter
$eagerLoading
Like
as false.
with(),
you can join with one or multiple relations; you may cus-
tomize the relation queries on-the-y; you may join with nested relations;
and you may mix the use of
with()
and
joinWith().
For example,
$customers = Customer::find()->joinWith([
'orders' => function ($query) {
$query->andWhere(['>', 'subtotal', 100]);
},
])->with('country')
->all();
Sometimes when joining two tables, you may need to specify some extra
conditions in the
ON
part of the JOIN query. This can be done by calling the
yii\db\ActiveQuery::onCondition()
method like the following:
// SELECT `customer`.* FROM `customer`
// LEFT JOIN `order` ON `order`.`customer_id` = `customer`.`id` AND `order
`.`status` = 1
//
// SELECT * FROM `order` WHERE `customer_id` IN (...)
$customers = Customer::find()->joinWith([
'orders' => function ($query) {
$query->onCondition(['order.status' => Order::STATUS_ACTIVE]);
268
CHAPTER 6.
WORKING WITH DATABASES
},
])->all();
This above query brings back
all
customers, and for each customer it brings
back all active orders. Note that this diers from our earlier example which
only brings back customers who have at least one active orders.
Info: When
yii\db\ActiveQuery
is specied with a condition
via [[onCondition(), the condition will be put in the
ON
part if
the query involves a JOIN query. If the query does not involve
JOIN, the on-condition will be automatically appended to the
WHERE
part of the query.
Inverse Relations
Relation declarations are often reciprocal between two Active Record classes.
For example,
Customer is
Customer
related back to
Order via the orders
customer relation.
related to
via the
relation, and
Order
is
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'])
;
}
}
Now consider the following piece of code:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
$order = $customer->orders[0];
// SELECT * FROM `customer` WHERE `id` = 123
$customer2 = $order->customer;
// displays "not the same"
echo $customer2 === $customer ? 'same' : 'not the same';
We would think
$customer
and
$customer2
are the same, but they are not!
Actually they do contain the same customer data, but they are dierent ob-
6.3.
ACTIVE RECORD
jects. When accessing
269
$order->customer,
$customer2.
an extra SQL statement is executed
to populate a new object
To avoid the redundant execution of the last SQL statement in the above
example, we should tell Yii that
calling the
inverseOf()
customer
is an
inverse relation
of
orders
by
method like shown below:
class Customer extends ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->
inverseOf('customer');
}
}
With this modied relation declaration, we will have:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
$order = $customer->orders[0];
// No SQL will be executed
$customer2 = $order->customer;
// displays "same"
echo $customer2 === $customer ? 'same' : 'not the same';
Note: Inverse relations cannot be dened for relations involving
a junction table. That is, if a relation is dened with
viaTable(),
you should not call
inverseOf()
via()
or
further.
6.3.11 Saving Relations
When working with relational data, you often need to establish relationships
between dierent data or destroy existing relationships. This requires setting
proper values for the columns that dene the relations. Using Active Record,
you may end up writing the code like the following:
$customer = Customer::findOne(123);
$order = new Order();
$order->subtotal = 100;
// ...
// setting the attribute that defines the "customer" relation in Order
$order->customer_id = $customer->id;
$order->save();
Active Record provides the
this task more nicely:
link()
method that allows you to accomplish
270
CHAPTER 6.
WORKING WITH DATABASES
$customer = Customer::findOne(123);
$order = new Order();
$order->subtotal = 100;
// ...
$order->link('customer', $customer);
The
link() method requires you to specify the relation name and the target
Active Record instance that the relationship should be established with. The
method will modify the values of the attributes that link two Active Record
instances and save them to the database. In the above example, it will set the
customer_id attribute of the Order instance to be the value of
of the Customer instance and then save it to the database.
Note:
the
id
attribute
You cannot link two newly created Active Record in-
stances.
The benet of using
link()
is even more obvious when a relation is dened
via a junction table. For example, you may use the following code to link an
Order
instance with an
Item
instance:
$order->link('items', $item);
The above code will automatically insert a row in the
order_item
junction
table to relate the order with the item.
Info: The
link() method will NOT perform any data validation
while saving the aected Active Record instance. It is your responsibility to validate any input data before calling this method.
The opposite operation to
link()
is
unlink()
which breaks an existing
relationship between two Active Record instances. For example,
$customer = Customer::find()->with('orders')->all();
$customer->unlink('orders', $customer->orders[0]);
By default, the
unlink() method will set the foreign key value(s) that specify
the existing relationship to be null. You may, however, choose to delete the
table row that contains the foreign key value by passing the
$delete parameter
as true to the method.
When a junction table is involved in a relation, calling
unlink()
will
cause the foreign keys in the junction table to be cleared, or the deletion of
the corresponding row in the junction table if
$delete
is true.
6.3.12 Cross-Database Relations
Active Record allows you to declare relations between Active Record classes
that are powered by dierent databases.
The databases can be of dier-
ent types (e.g. MySQL and PostgreSQL, or MS SQL and MongoDB), and
they can run on dierent servers. You can use the same syntax to perform
relational queries. For example,
6.3.
ACTIVE RECORD
271
// Customer is associated with the "customer" table in a relational database
(e.g. MySQL)
class Customer extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'customer';
}
}
public function getComments()
{
// a customer has many comments
return $this->hasMany(Comment::className(), ['customer_id' => 'id'])
;
}
// Comment is associated with the "comment" collection in a MongoDB database
class Comment extends \yii\mongodb\ActiveRecord
{
public static function collectionName()
{
return 'comment';
}
}
public function getCustomer()
{
// a comment has one customer
return $this->hasOne(Customer::className(), ['id' => 'customer_id'])
;
}
$customers = Customer::find()->with('comments')->all();
You can use most of the relational query features that have been described
in this section.
Note: Usage of
joinWith()
cross-database JOIN queries.
is limited to databases that allow
For this reason, you cannot use
this method in the above example because MongoDB does not
support JOIN.
6.3.13 Customizing Query Classes
By default, all Active Record queries are supported by
yii\db\ActiveQuery.
To use a customized query class in an Active Record class, you should override the
yii\db\ActiveRecord::find()
method and return an instance of
your customized query class. For example,
namespace app\models;
272
CHAPTER 6.
WORKING WITH DATABASES
use yii\db\ActiveRecord;
use yii\db\ActiveQuery;
class Comment extends ActiveRecord
{
public static function find()
{
return new CommentQuery(get_called_class());
}
}
class CommentQuery extends ActiveQuery
{
// ...
}
Now whenever you are performing a query (e.g.
a relation (e.g.
of
CommentQuery
hasOne())
instead of
Comment,
ActiveQuery.
with
find(), findOne())
or dening
you will be working with an instance
Tip: In big projects, it is recommended that you use customized
query classes to hold most query-related code so that the Active
Record classes can be kept clean.
You can customize a query class in many creative ways to improve your
query building experience. For example, you can dene new query building
methods in a customized query class:
class CommentQuery extends ActiveQuery
{
public function active($state = true)
{
return $this->andWhere(['active' => $state]);
}
}
where(), you usually should call andWhere()
orWhere() to append additional conditions when dening new
Note: Instead of calling
or
query building methods so that any existing conditions are not
overwritten.
This allows you to write query building code like the following:
$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();
You can also use the new query building methods when dening relations
about
Comment
or performing relational query:
class Customer extends \yii\db\ActiveRecord
{
public function getActiveComments()
6.4.
DATABASE MIGRATION
273
{
}
return $this->hasMany(Comment::className(), ['customer_id' => 'id'])
->active();
}
$customers = Customer::find()->with('activeComments')->all();
// or alternatively
$customers = Customer::find()->with([
'comments' => function($q) {
$q->active();
}
])->all();
Info: In Yii 1.1, there is a concept called
scope.
Scope is no longer
directly supported in Yii 2.0, and you should use customized
query classes and query methods to achieve the same goal.
6.4 Database Migration
During the course of developing and maintaining a database-driven application, the structure of the database being used evolves just like the source
code does. For example, during the development of an application, a new
table may be found necessary; after the application is deployed to production, it may be discovered that an index should be created to improve the
query performance; and so on. Because a database structure change often
requires some source code changes, Yii supports the so-called
database mi-
gration feature that allows you to keep track of database changes in terms
of database migrations which are version-controlled together with the source
code.
The following steps show how database migration can be used by a team
during development:
1. Tim creates a new migration (e.g.
creates a new table, changes a
column denition, 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 receives the new migration.
4. Doug applies the migration to his local development database, thereby
synchronizing his database to reect the changes that Tim has made.
274
CHAPTER 6.
WORKING WITH DATABASES
And the following steps show how to deploy a new release with database
migrations to production:
1. Scott creates a release tag for the project repository that contains some
new database migrations.
2. Scott updates the source code on the production server to the release
tag.
3. Scott applies any accumulated database migrations to the production
database.
Yii provides a set of migration command line tools that allow you to:
•
•
•
•
•
create new migrations;
apply migrations;
revert migrations;
re-apply migrations;
show migration history and status.
All these tools are accessible through the command
yii migrate.
In this
section we will describe in detail how to accomplish various tasks using these
tools. You may also get the usage of each tool via the help command
yii
help migrate.
6.4.1 Creating Migrations
To create a new migration, run the following command:
yii migrate/create <name>
The required
name
argument gives a brief description about the new migra-
tion. For example, if the migration is about creating a new table named
you may use the name
create_news_table
news,
and run the following command:
yii migrate/create create_news_table
Note:
Because the
name
argument will be used as part of the
generated migration class name, it should only contain letters,
digits, and/or underscore characters.
The above command will create a new PHP class le named
.php
in the
@app/migrations
directory.
which mainly declares a migration class
the skeleton code:
<?php
use yii\db\Schema;
use yii\db\Migration;
m150101_185401_create_news_table
The le contains the following code
m150101_185401_create_news_table with
6.4.
DATABASE MIGRATION
275
class m150101_185401_create_news_table extends Migration
{
public function up()
{
}
}
public function down()
{
echo "m101129_185401_create_news_table cannot be reverted.\n";
return false;
}
Each database migration is dened as a PHP class extending from
\Migration.
yii\db
The migration class name is automatically generated in the
m<YYMMDD_HHMMSS>_<Name>, where
• <YYMMDD_HHMMSS> refers to the UTC
format of
datetime at which the migration
creation command is executed.
• <Name>
is the same as the value of the
name
argument that you provide
to the command.
In the migration class, you are expected to write code in the
up() method that
makes changes to the database structure. You may also want to write code
in the
down()
method to revert the changes made by
up().
The
up
method is
invoked when you upgrade the database with this migration, while the
down()
method is invoked when you downgrade the database. The following code
shows how you may implement the migration class to create a
news
table:
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
$this->createTable('news', [
'id' => Schema::TYPE_PK,
'title' => Schema::TYPE_STRING . ' NOT NULL',
'content' => Schema::TYPE_TEXT,
]);
}
public function down()
{
$this->dropTable('news');
}
}
Info: Not all migrations are reversible. For example, if the
up()
method deletes a row of a table, you may not be able to recover
276
CHAPTER 6.
this row in the
down()
WORKING WITH DATABASES
method. Sometimes, you may be just too
down(),
because it is not very common
to revert database migrations.
In this case, you should return
lazy to implement the
false
in the
down()
method to indicate that the migration is not
reversible.
The base migration class
via the
db property.
yii\db\Migration
exposes a database connection
You can use it to manipulate the database schema using
the methods as described in Working with Database Schema.
Rather than using physical types, when creating a table or column you
should use
abstract types
DBMS. The
so that your migrations are independent of specic
yii\db\Schema
class denes a set of constants to represent the
supported abstract types. These constants are named in the format of
TYPE_
<Name>. For example, TYPE_PK refers to auto-incremental primary key type;
TYPE_STRING refers to a string type. When a migration is applied to a particular database, the abstract types will be translated into the corresponding
physical types. In the case of MySQL,
NOT NULL AUTO_INCREMENT PRIMARY KEY,
TYPE_PK will be turned into int(11)
TYPE_STRING becomes varchar(255).
while
You can append additional constraints when using abstract types. In the
above example,
NOT NULL
is appended to
Schema::TYPE_STRING
to specify that
the column cannot be null.
Info: The mapping between abstract types and physical types is
specied by the
$typeMap property in each concrete QueryBuilder
class.
Transactional Migrations
While performing complex DB migrations, it is important to ensure each migration to either succeed or fail as a whole so that the database can maintain
integrity and consistency. To achieve this goal, it is recommended that you
enclose the DB operations of each migration in a transaction.
An even easier way of implementing transactional migrations is to put
safeUp() and safeDown() methods. These two methods
down() in that they are enclosed implicitly in a transac-
migration code in the
dier from
up()
and
tion. As a result, if any operation in these methods fails, all prior operations
will be rolled back automatically.
In the following example, besides creating the
news
an initial row into this table.
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
table we also insert
6.4.
DATABASE MIGRATION
277
public function safeUp()
{
$this->createTable('news', [
'id' => 'pk',
'title' => Schema::TYPE_STRING . ' NOT NULL',
'content' => Schema::TYPE_TEXT,
]);
}
$this->insert('news', [
'title' => 'test 1',
'content' => 'content 1',
]);
public function safeDown()
{
$this->delete('news', ['id' => 1]);
$this->dropTable('news');
}
}
safeUp(), you
safeDown(). In the above example we
a row in safeUp(); while in safeDown()
Note that usually when you perform multiple DB operations in
should reverse their execution order in
rst create the table and then insert
we rst delete the row and then drop the table.
Note: Not all DBMS support transactions. And some DB queries
cannot be put into a transaction.
For some examples, please
17 . If this is the case, you should still
refer to implicit commit
implement
up()
and
down(),
instead.
Database Accessing Methods
The base migration class
yii\db\Migration
provides a set of methods to
let you access and manipulate databases. You may nd these methods are
yii\db\Command class.
yii\db\Migration::createTable() method allows you to
create a new table, just like yii\db\Command::createTable() does.
The benet of using the methods provided by yii\db\Migration is that
you do not need to explicitly create yii\db\Command instances and the exenamed similarly as the DAO methods provided by the
For example, the
cution of each method will automatically display useful messages telling you
what database operations are done and how long they take.
Below is the list of all these database accessing methods:
•
•
•
•
17
execute(): executing a SQL statement
insert(): inserting a single row
batchInsert(): inserting multiple rows
update(): updating rows
http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html
278
CHAPTER 6.
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
WORKING WITH DATABASES
delete(): deleting rows
createTable(): creating a table
renameTable(): renaming a table
dropTable(): removing a table
truncateTable(): removing all rows in a table
addColumn(): adding a column
renameColumn(): renaming a column
dropColumn(): removing a column
alterColumn(): altering a column
addPrimaryKey(): adding a primary key
dropPrimaryKey(): removing a primary key
addForeignKey(): adding a foreign key
dropForeignKey(): removing a foreign key
createIndex(): creating an index
dropIndex(): removing an index
Info:
yii\db\Migration does not provide a database query method.
This is because you normally do not need to display extra message about retrieving data from a database.
It is also because
you can use the powerful Query Builder to build and run complex
queries.
6.4.2 Applying Migrations
To upgrade a database to its latest structure, you should apply all available
new migrations using the following command:
yii migrate
This command will list all migrations that have not been applied so far. If
you conrm that you want to apply these migrations, it will run the
or
safeUp()
up()
method in every new migration class, one after another, in the
order of their timestamp values. If any of the migrations fails, the command
will quit without applying the rest of the migrations.
For each migration that has been successfully applied, the command will
insert a row into a database table named
migration
to record the successful
application of the migration. This will allow the migration tool to identify
which migrations have been applied and which have not.
Info: The migration tool will automatically create the
table in the database specied by the
migration
db option of the command.
By default, the database is specied by the
db
application com-
ponent.
Sometimes, you may only want to apply one or a few new migrations, instead
of all available migrations. You can do so by specifying the number of mi-
6.4.
DATABASE MIGRATION
279
grations that you want to apply when running the command. For example,
the following command will try to apply the next three available migrations:
yii migrate 3
You can also explicitly specify a particular migration to which the database
should be migrated by using the
migrate/to
command in one of the following
formats:
yii migrate/to 150101_185401
specify the migration
yii migrate/to "2015-01-01 18:54:01"
be parsed by strtotime()
yii migrate/to m150101_185401_create_news_table
yii migrate/to 1392853618
# using timestamp to
# using a string that can
# using full name
# using UNIX timestamp
If there are any unapplied migrations earlier than the specied one, they will
all be applied before the specied migration is applied.
If the specied migration has already been applied before, any later applied migrations will be reverted.
6.4.3 Reverting Migrations
To revert (undo) one or multiple migrations that have been applied before,
you can run the following command:
yii migrate/down
yii migrate/down 3
# revert the most recently applied migration
# revert the most 3 recently applied migrations
Note: Not all migrations are reversible.
Trying to revert such
migrations will cause an error and stop the entire reverting process.
6.4.4 Redoing Migrations
Redoing migrations means rst reverting the specied migrations and then
applying again. This can be done as follows:
yii migrate/redo
yii migrate/redo 3
# redo the last applied migration
# redo the last 3 applied migrations
Note: If a migration is not reversible, you will not be able to redo
it.
6.4.5 Listing Migrations
To list which migrations have been applied and which are not, you may use
the following commands:
280
CHAPTER 6.
WORKING WITH DATABASES
yii migrate/history
# showing the last 10 applied migrations
yii migrate/history 5
# showing the last 5 applied migrations
yii migrate/history all # showing all applied migrations
yii migrate/new
yii migrate/new 5
yii migrate/new all
# showing the first 10 new migrations
# showing the first 5 new migrations
# showing all new migrations
6.4.6 Modifying Migration History
Instead of actually applying or reverting migrations, sometimes you may
simply want to mark that your database has been upgraded to a particular
migration. This often happens when you manually change the database to
a particular state and you do not want the migration(s) for that change to
be re-applied later. You can achieve this goal with the following command:
yii migrate/mark 150101_185401
specify the migration
yii migrate/mark "2015-01-01 18:54:01"
can be parsed by strtotime()
yii migrate/mark m150101_185401_create_news_table
yii migrate/mark 1392853618
The command will modify the
migration
# using timestamp to
# using a string that
# using full name
# using UNIX timestamp
table by adding or deleting cer-
tain rows to indicate that the database has been applied migrations to the
specied one. No migrations will be applied or reverted by this command.
6.4.7 Customizing Migrations
There are several ways to customize the migration command.
Using Command Line Options
The migration command comes with a few command-line options that can
be used to customize its behaviors:
• interactive:
boolean (defaults to true), species whether to perform
migrations in an interactive mode.
When this is true, the user will
be prompted before the command performs certain actions. You may
want to set this to false if the command is being used in a background
process.
• migrationPath:
string (defaults to
@app/migrations),
species the direc-
tory storing all migration class les. This can be specied as either a
directory path or a path alias. Note that the directory must exist, or
the command may trigger an error.
• migrationTable:
string (defaults to
migration),
species the name of the
database table for storing migration history information.
The table
will be automatically created by the command if it does not exist. You
6.4.
DATABASE MIGRATION
281
may also manually create it using the structure
primary key, apply_time integer.
• db: string (defaults to db), species
version varchar(255)
the ID of the database application
component. It represents the database that will be migrated using this
command.
• templateFile:
string (defaults to
@yii/views/migration.php),
species the
path of the template le that is used for generating skeleton migration
class les. This can be specied as either a le path or a path alias.
The template le is a PHP script in which you can use a predened
variable named
$className
to get the migration class name.
The following example shows how you can use these options.
forum module whose migration les
migrations directory, we can use the following
For example, if we want to migrate a
are located within the module's
command:
# migrate the migrations in a forum module non-interactively
yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0
Conguring Command Globally
Instead of entering the same option values every time you run the migration
command, you may congure it once for all in the application conguration
like shown below:
return [
'controllerMap' => [
'migrate' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationTable' => 'backend_migration',
],
],
];
With the above conguration, each time you run the migration command,
the
backend_migration
table will be used to record the migration history. You
no longer need to specify it via the
migrationTable
command-line option.
6.4.8 Migrating Multiple Databases
By default, migrations are applied to the same database specied by the
db
application component.
If you want them to be applied to a dierent
database, you may specify the
db
command-line option like shown below,
yii migrate --db=db2
The above command will apply migrations to the
db2
Sometimes it may happen that you want to apply
database.
some
of the migrations
to one database, while some others to another database. To achieve this goal,
when implementing a migration class you should explicitly specify the DB
component ID that the migration would use, like the following:
282
CHAPTER 6.
WORKING WITH DATABASES
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
public function init()
{
$this->db = 'db2';
parent::init();
}
}
The above migration will be applied to
database through the
db
db2,
even if you specify a dierent
command-line option.
Note that the migration
history will still be recorded in the database specied by the
db
command-
line option.
If you have multiple migrations that use the same database, it is recommended that you create a base migration class with the above
init()
code.
Then each migration class can extend from this base class.
Tip: Besides setting the
db
property, you can also operate on
dierent databases by creating new database connections to them
in your migration classes. You then use the DAO methods with
these connections to manipulate dierent databases.
Another strategy that you can take to migrate multiple databases is to keep
migrations for dierent databases in dierent migration paths.
Then you
can migrate these databases in separate commands like the following:
yii migrate --migrationPath=@app/migrations/db1 --db=db1
yii migrate --migrationPath=@app/migrations/db2 --db=db2
...
The rst command will apply migrations in
@app/migrations/db1 to the db1
@app/migrations/db2
database, the second command will apply migrations in
to
db2,
and so on.
6.4.
DATABASE MIGRATION
283
Error: not existing le: https://github.com/yiisoft/yii2-sphinx/blob/master/docs/guide/R
284
CHAPTER 6.
WORKING WITH DATABASES
Error: not existing le: https://github.com/yiisoft/yii2-redis/blob/master/docs/
6.4.
DATABASE MIGRATION
285
Error: not existing le: https://github.com/yiisoft/yii2-mongodb/blob/master/docs/guid
286
CHAPTER 6.
WORKING WITH DATABASES
Error: not existing le: https://github.com/yiisoft/yii2-elasticsearch/blob/maste
Chapter 7
Getting Data from Users
7.1 Creating Forms
The primary way of using forms in Yii is through
yii\widgets\ActiveForm.
This approach should be preferred when the form is based upon a model.
Additionally, there are some useful methods in
yii\helpers\Html
that are
typically used for adding buttons and help text to any form.
A form, that is displayed on the client side, will in most cases have a
corresponding model which is used to validate its input on the server side
(Check the Validating Input section for more details on validation). When
creating model-based forms, the rst step is to dene the model itself. The
model can be either based upon an Active Record class, representing some
data from the database, or a generic Model class (extending from
\Model) to capture arbitrary input, for example a login form.
yii\base
In the following
example we show, how a generic Model is used for a login form:
<?php
class LoginForm extends \yii\base\Model
{
public $username;
public $password;
}
public function rules()
{
return [
// define validation rules here
];
}
In the controller, we will pass an instance of that model to the view, wherein
the
ActiveForm
widget is used to display the form:
<?php
use yii\helpers\Html;
287
288
CHAPTER 7.
GETTING DATA FROM USERS
use yii\widgets\ActiveForm;
$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,
ActiveForm::begin()
not only creates a form instance,
but also marks the beginning of the form. All of the content placed between
ActiveForm::begin()
HTML
<form>
tag.
and
ActiveForm::end()
will be wrapped within the
As with any widget, you can specify some options as
to how the widget should be congured 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>
API documentation of
tag. For all available options, please refer to the
yii\widgets\ActiveForm.
In order to create a form element in the form, along with the element's
ActiveForm::field()
yii\widgets\ActiveField.
label, and any applicable JavaScript validation, the
method is called, which returns an instance of
When the result of this method is echoed directly, the result is a regular
(text) input. To customize the output, you can chain additional methods of
ActiveField
to this call:
// a password input
<?= $form->field($model, 'password')->passwordInput() ?>
// adding a hint and a customized label
<?= $form->field($model, 'username')->textInput()->hint('Please enter your
name')->label('Name') ?>
// creating a HTML5 email input element
<?= $form->field($model, 'email')->input('email') ?>
This will create all the
<label>, <input>
and other tags according to the
template dened by the form eld. The name of the input eld is determined
automatically from the model's form name and the attribute's name. For
example, the name for the input eld for the
example will be
LoginForm[username].
username
attribute in the above
This naming rule will result in an array
of all attributes for the login form to be available in
$_POST['LoginForm']
on
the server side.
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 les or selecting multiple items you may specify it by appending
7.2.
[]
VALIDATING INPUT
289
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']);
Additional HTML tags can be added to the form using plain HTML or using
Html-helper
Html::submitButton().
the methods from the
with
class like it is done in the above example
Tip: If you are using Twitter Bootstrap CSS in your application
yii\bootstrap\ActiveForm instead of yii
\widgets\ActiveForm, which is an extension of the ActiveForm
you may want to use
class that adds some additional styling that works well with the
bootstrap CSS framework.
Tip: in order to style required elds with asterisk you can use
the following CSS:
div.required label:after {
content: " *";
color: red;
}
The next section Validating Input handles the validation of the submitted
form data on the server side as well as ajax- and client side validation.
To read about more complex usage of forms, you may want to check out
the following sections:
•
Collecting tabular input for collecting data for multiple models of the
same kind.
•
Complex Forms with Multiple Models for handling multiple dierent
models in the same form.
•
Uploading Files on how to use forms for uploading les.
7.2 Validating Input
As a rule of thumb, you should never trust the data received from end users
and should always validate it before putting it to good use.
Given a model populated with user inputs, you can validate the inputs
by calling the
yii\base\Model::validate()
method. The method will re-
turn a boolean value indicating whether the validation succeeded or not. If
not, you may get the error messages from the
property. For example,
yii\base\Model::$errors
290
CHAPTER 7.
GETTING DATA FROM USERS
$model = new \app\models\ContactForm();
// populate model attributes with user inputs
$model->load(\Yii::$app->request->post());
// which is equivalent to the following:
// $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;
}
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
\base\Model::rules()
idation rules for the
yii
method. The following example shows how the val-
ContactForm
model are declared:
public function rules()
{
return [
// the name, email, subject and body attributes are required
[['name', 'email', 'subject', 'body'], 'required'],
}
The
];
// the email attribute should be a valid email address
['email', 'email'],
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
['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', ...],
7.2.
]
VALIDATING INPUT
291
// 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.
•
a fully qualied validator class name. 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:
1. Determine which attributes should be validated by getting the attribute list from
yii\base\Model::scenarios() using the current scenario.
These attributes are called
active attributes.
2. Determine which validation rules should be used by getting the rule list
from
yii\base\Model::rules()
rules are called
active rules.
using the current
scenario.
These
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
scenarios()
rules().
only if it is an active attribute declared in
one or multiple active rules declared in
and is associated with
Customizing Error Messages
Most validators have default error messages that will be added to the model
being validated when its attributes fail the validation.
required
For example, the
validator will add a message Username cannot be blank.
model when the
username
to a
attribute fails the rule using this validator.
You can customize the error message of a rule by specifying the
property when declaring the rule, like the following,
message
292
CHAPTER 7.
GETTING DATA FROM USERS
public function rules()
{
return [
['username', 'required', 'message' => 'Please choose a username.'],
];
}
Some validators may support additional error messages to more precisely
number valtooBig and tooSmall to describe the validation failure when
describe dierent causes of validation failures. For example, the
idator supports
the value being validated is too big and too small, respectively.
You may
congure these error messages like conguring other properties of validators
in a validation rule.
Validation Events
When
yii\base\Model::validate() is called, it will call two methods that
you may override to customize the validation process:
• yii\base\Model::beforeValidate(): the default implementation will
trigger a yii\base\Model::EVENT_BEFORE_VALIDATE event. You may
either override this method or respond to this event to do some preprocessing 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.
• yii\base\Model::afterValidate(): the default implementation will
trigger a yii\base\Model::EVENT_AFTER_VALIDATE event. You may
either override this method or respond to this event to do some postprocessing 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
when
[
]
The
property to dene such conditions. For example,
['state', 'required', 'when' => function($model) {
return $model->country == 'USA';
}],
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)
7.2.
VALIDATING INPUT
293
If you also need to support client-side conditional validation, you should congure the
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';
}"],
Data Filtering
User inputs often need to be ltered 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 lter validator to perform more complex
data ltering.
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 conguring the the
yii\validators\Validator::isEmpty()
with a PHP callable. For example,
property
294
[
]
CHAPTER 7.
GETTING DATA FROM USERS
['agree', 'required', 'isEmpty' => function ($value) {
return empty($value);
}],
Note: Most validators do not handle empty inputs if their
yii
\base\Validator::skipOnEmpty property takes the default value
true. They will simply be skipped during validation if their associated attributes receive empty inputs. Among the core validators, only the
captcha, default, filter, required,
and
trim
valida-
tors will handle empty inputs.
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
validate()
method of the desired validator,
like the following:
$email = '
[email protected]';
$validator = new yii\validators\EmailValidator();
if ($validator->validate($email, $error)) {
echo 'Email is valid.';
} else {
echo $error;
}
Note:
Not all validators support this type 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
yii\base\DynamicModel
which supports declaring both attributes and
rules on the y. 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
}
7.2.
VALIDATING INPUT
The
of
295
yii\base\DynamicModel::validateData() method creates an instance
DynamicModel,
denes the attributes using the given data (name and
this example), and then calls
yii\base\Model::validate()
email
in
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();
}
if ($model->hasErrors()) {
// validation fails
} else {
// validation succeeds
}
After validation, you can check if the validation succeeded or not by calling
the
hasErrors() method, and then get the validation errors from the errors
property, like you do with a normal model. You may also access the dynamic
attributes dened 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 dened in terms of a model method or an anonymous function. The signature of the method/function is:
/**
* @param string $attribute the attribute currently being validated
* @param mixed $params the value of the "params" given in the rule
*/
function ($attribute, $params)
If an attribute fails the validation, the method/function should call
\Model::addError()
be retrieved back later to present to end users.
Below are some examples:
use yii\base\Model;
yii\base
to save the error message in the model so that it can
296
CHAPTER 7.
GETTING DATA FROM USERS
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) {
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
associated attributes receive empty inputs or if they have already
failed some validation rules.
If you want to make sure a rule
is always applied, you may congure the
skipOnError
skipOnEmpty
and/or
properties to be false in the rule declarations. For
example:
[
]
['country', 'validateCountry', 'skipOnEmpty' => false, '
skipOnError' => false],
Standalone Validators
yii\validators\Validator or
yii
\validators\Validator::validateAttribute() method. If an attribute
fails the validation, call yii\base\Model::addError() to save the error mesA standalone validator is a class extending
its child class. You may implement its validation logic by overriding the
sage in the model, like you do with inline validators. For example,
7.2.
VALIDATING INPUT
297
namespace app\components;
use yii\validators\Validator;
class CountryValidator extends Validator
{
public function validateAttribute($model, $attribute)
{
if (!in_array($model->$attribute, ['USA', 'Web'])) {
$this->addError($model, $attribute, 'The country must be either
"USA" or "Web".');
}
}
}
If you want your validator to support validating a value without a model,
yii\validators\Validator::validate(). You
yii\validators\Validator::validateValue() instead
you should also override
may also override
of
validateAttribute()
validate() because by
calling validateValue().
and
ods are implemented by
default the latter two meth-
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 nd out input errors
faster and thus provides a better user experience. You may use or implement
a validator that supports client-side validation
in addition to
server-side val-
idation.
Info: While client-side validation is desirable, it is not a must. Its
main purpose is to provide users with a better experience. Similar
to 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
yii\base\Model::validate(),
as described in the previous subsections.
Using Client-Side Validation
Many core validators support client-side validation out-of-the-box. All you
need to do is just use
For example,
yii\widgets\ActiveForm to build your HTML forms.
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
side.
namespace app\models;
use yii\base\Model;
use app\models\User;
inline validator which is only supported on the server
298
CHAPTER 7.
GETTING DATA FROM USERS
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'],
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 elds
and
password.
username
If you submit the form without entering anything, you will
nd the error messages requiring you to enter something appear right away
without any communication with the server.
<?php $form = yii\widgets\ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<?= Html::submitButton('Login') ?>
<?php yii\widgets\ActiveForm::end(); ?>
Behind the scene,
yii\widgets\ActiveForm
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 eld or submit the form, the client-side validation JavaScript will be
triggered.
If you want to turn o client-side validation completely, you may congure the
yii\widgets\ActiveForm::$enableClientValidation property to
be false. You may also turn o client-side validation of individual input elds
by conguring their
yii\widgets\ActiveField::$enableClientValidation
property to be false. When
enableClientValidation
is congured at both the
input eld level and the form level, the former will take precedence.
7.2.
VALIDATING INPUT
299
Implementing Client-Side Validation
To create a validator that supports client-side validation, you should implement the
yii\validators\Validator::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 predened
variables:
• attribute: the name of the attribute
• value: the value being validated.
• messages: an array used to hold the
being validated.
validation error messages for the
attribute.
• deferred:
an array which deferred objects can be pushed into (explained
in the next subsection).
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 app\components;
use yii\validators\Validator;
use app\models\Status;
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, JSON_UNESCAPED_SLASHES |
JSON_UNESCAPED_UNICODE);
return <<<JS
if (!$.inArray(value, $statuses)) {
messages.push($message);
}
JS;
}
}
300
CHAPTER 7.
GETTING DATA FROM USERS
Tip: The above code is given mainly to demonstrate how to support 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:
[
]
['status', 'in', 'range' => Status::find()->select('id')->
asArray()->column()],
Deferred Validation
If you need to perform asynchronous client-side validation, you can create
1
Deferred objects . 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;
}
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()
In the above, the
method when the asynchronous callback is hit. The following example shows
how to validate the dimensions of an uploaded image le 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);
1
http://api.jquery.com/category/deferred-object/
7.2.
JS;
}
VALIDATING INPUT
301
deferred.push(def);
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
add()
deferred
array is equipped with a shortcut method
which automatically creates a Deferred object and adds it to the
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 a single input eld, congure the
property of that eld to be true and specify a unique form
use yii\widgets\ActiveForm;
$form = ActiveForm::begin([
'id' => 'registration-form',
]);
id:
enableAjaxValidation
302
CHAPTER 7.
GETTING DATA FROM USERS
echo $form->field($model, 'username', ['enableAjaxValidation' => true]);
// ...
ActiveForm::end();
To enable AJAX validation for the whole form, congure
enableAjaxValidation
to be true at the form level:
$form = ActiveForm::begin([
'id' => 'contact-form',
'enableAjaxValidation' => true,
]);
Note: When the
enableAjaxValidation
property is congured at
both the input eld level and the form level, the former will take
precedence.
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 the
controller actions:
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post()))
{
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
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 eort.
7.3 Uploading Files
Uploading les in Yii is done via a form model, its validation rules and some
controller code. Let's review what's required to handle uploads properly.
7.3.1 Uploading single le
First of all, you need to create a model that will handle le uploads. Create
models/UploadForm.php
namespace app\models;
use yii\base\Model;
with the following content:
7.3.
UPLOADING FILES
303
use yii\web\UploadedFile;
/**
* UploadForm is the model behind the upload form.
*/
class UploadForm extends Model
{
/**
* @var UploadedFile file attribute
*/
public $file;
}
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['file'], 'file'],
];
}
UploadForm with an attribute file
<input type="file"> in the HTML form. The attribute has
rule named file that uses FileValidator.
In the code above, we've created a model
that will become
the validation
Form view
Next, create a view that will render the form:
<?php
use yii\widgets\ActiveForm;
?>
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/formdata']]) ?>
<?= $form->field($model, 'file')->fileInput() ?>
<button>Submit</button>
<?php ActiveForm::end() ?>
The
'enctype' => 'multipart/form-data' is necessary
fileInput() represents a form input eld.
because it allows le up-
loads.
Controller
Now create the controller that connects the form and the model together:
namespace app\controllers;
use Yii;
304
CHAPTER 7.
GETTING DATA FROM USERS
use yii\web\Controller;
use app\models\UploadForm;
use yii\web\UploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->file = UploadedFile::getInstance($model, 'file');
if ($model->file && $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(...). UploadedFile
does not run the model validation, rather it only provides information about
the uploaded le. Therefore, you need to run the validation manually via
$model->validate()
to trigger the
FileValidator.
The validator expects that
the attribute is an uploaded le, as you see in the core framework code:
if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) {
return [$this->uploadRequired, []];
}
If the validation is successful, then we're saving the le:
$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->
file->extension);
If you're using the basic project template, then folder
created under
uploads
should be
web.
That's it. Load the page and try uploading. Uploads should end up in
basic/web/uploads.
7.3.2 Validation
It's often required to adjust validation rules to accept certain les only or
require uploading. Below we'll review some common rule congurations.
Required
If you need to make the le upload mandatory, use
lowing:
skipOnEmpty
like the fol-
7.3.
UPLOADING FILES
305
public function rules()
{
return [
[['file'], 'file', 'skipOnEmpty' => false],
];
}
MIME type
It is wise to validate the type of le uploaded. FileValidator has the property
$extensions
for this purpose:
public function rules()
{
return [
[['file'], 'file', 'extensions' => 'gif, jpg'],
];
}
By default it will validate against le content mime type corresponding to
extension specied. For gif it will be
image/gif,
for
jpg
it will be
image/jpeg.
Note that some mime types can't be detected properly by PHP's leinfo
file validator. For example, csv les are detected
as text/plain instead of text/csv. You can turn o such behavior by setting
checkExtensionByMimeType to false and specifying mime types manually:
extension that is used by
public function rules()
{
return [
[['file'], 'file', 'checkExtensionByMimeType' => false, 'extensions'
=> 'csv', 'mimeTypes' => 'text/plain'],
];
}
List of common media types
2
Image properties
If you upload an image,
ImageValidator may come in handy.
It veries if an
attribute received a valid image that can be then either saved or processed
3
using the Imagine Extension .
7.3.3 Uploading multiple les
If you need to upload multiple les at once, some adjustments are required.
Model:
2
http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_
types
3
https://github.com/yiisoft/yii2/tree/master/extensions/imagine
306
CHAPTER 7.
GETTING DATA FROM USERS
class UploadForm extends Model
{
/**
* @var UploadedFile|Null file attribute
*/
public $file;
}
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['file'], 'file', 'maxFiles' => 10], // <--- here!
];
}
View:
<?php
use yii\widgets\ActiveForm;
$form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data'
]]);
?>
<?= $form->field($model, 'file[]')->fileInput(['multiple' => true]) ?>
<button>Submit</button>
<?php ActiveForm::end(); ?>
The dierence is the following line:
<?= $form->field($model, 'file[]')->fileInput(['multiple' => true]) ?>
Controller:
namespace app\controllers;
use
use
use
use
Yii;
yii\web\Controller;
app\models\UploadForm;
yii\web\UploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->file = UploadedFile::getInstances($model, 'file');
if ($model->file && $model->validate()) {
7.4.
COLLECTING TABULAR INPUT
307
foreach ($model->file as $file) {
$file->saveAs('uploads/' . $file->baseName . '.' . $file
->extension);
}
}
}
}
}
return $this->render('upload', ['model' => $model]);
UploadedFile
UploadedFile::getInstance(
There are two dierences from single le upload. First is that
::getInstances($model, 'file'); is used instead of
$model, 'file');. The former returns instances for
the latter gives you only a single instance.
we're doing
foreach
all
uploaded les while
The second dierence is that
and saving each le.
7.4 Collecting tabular input
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 a namevalue pair and is represented by a
Setting
active record model. This kind of
form is also often referred to as tabular input. In contrast to this, handling
dierent models of dierent kind, is handled in the section Complex Forms
with Multiple Models.
The following shows how to implement tabular input with Yii.
There are three dierent situations to cover, which have to be handled
slightly dierent:
- Updating a xed set of records from the database -
Creating a dynamic set of new records - Updating, creating and deleting of
records on one page
In contrast to the single model forms explained before, we are working
with an array of models now. This array is passed to the view to display the
input elds for each model in a table like style and we will use helper methods
of
yii\base\Model
that allow loading and validating multiple models at
once:
• Model::loadMultiple() load post data into an array of models.
• Model::validateMultiple() validates an array of models.
Updating a xed set of records
Let's start with the controller action:
<?php
namespace app\controllers;
use Yii;
use yii\base\Model;
308
CHAPTER 7.
GETTING DATA FROM USERS
use yii\web\Controller;
use app\models\Setting;
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 the
database to populate an array indexed by models primary keys. These will
be later used to identify form elds.
Model::loadMultiple() lls multiple
Model::validateMultiple()
models with the form data coming from POST and
validates all models at once. As we have validated our models before, using
validateMultiple(),
we're now passing
false
as a parameter to
save()
to not
run validation twice.
Now the form that's in
update
view:
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
$form = ActiveForm::begin();
foreach ($settings as $index => $setting) {
echo $form->field($setting, "[$index]value")->label($setting->name);
}
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()
Model
determines which model to ll with which values.
Creating a dynamic set of new records
Creating new records is similar to updating, except the part, where we instantiate the models:
7.5.
COMPLEX FORMS WITH MULTIPLE MODELS
309
public function actionCreate()
{
$count = count(Yii::$app->request->post('Setting', []));
$settings = [new Setting()];
for($i = 1; $i < $count; $i++) {
$settings[] = new Setting();
}
}
// ...
Here we create an initial
$settings
array containing one model by default so
that always at least one text eld will be visible in the view. Additionally
we add more models for each line of input we may have received.
In the view you can use javascript to add new input lines dynamically.
Combining Update, Create and Delete on one page
Note: This section is under development.
It has no content yet.
TBD
7.5 Complex Forms with Multiple Models
In complex user interfaces it can happen that a user has to ll in data in one
form that has to be saved in dierent tables in the database. The concept
of Yii forms allows you to build these forms with nearly no more complexity
compared to single model forms.
Same as with one model you follow the following schema for validation
on the server side:
1. instantiate model classes
2. populate the models attributes with input data
3. validate all models
4. If validation passes for all models, save them
5. If validation fails or no data has been submitted, display the form by
passing all model instances to the view
In the following we show an example for using multiple models in a form. . . TBD
310
CHAPTER 7.
GETTING DATA FROM USERS
7.5.1 Multiple models example
Note: This section is under development.
It has no content yet.
TBD
7.5.2 Dependend models
Note: This section is under development.
It has no content yet.
TBD
Chapter 8
Displaying Data
8.1 Data Formatter
For formatting of outputs Yii provides a formatter class to make data more
readable for users.
yii\i18n\Formatter
as an application component named
is a helper class that is registered
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 dierent ways.
1. Using the formatting methods (all formatter methods prexed 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)
format() method and the format name. This method is also
used by widgets like yii\grid\GridView and yii\widgets\DetailView
2. Using the
where you can specify the data format of a column in the widget conguration.
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%
311
312
CHAPTER 8.
DISPLAYING DATA
1 is
All output of the formatter is localized when the PHP intl extension
installed. You can congure the
locale property of the formatter
language is used as the locale.
If not congured, the application
section on internationalization for more details.
for this.
See the
The Formatter will then
choose the correct format for dates and numbers according to the locale
including names of month and weekdays translated to the current language.
timeZone which will also be taken from
Date formats are also aected by the
the application
timeZone
if not congured explicitly.
For example the date format call will output dierent results for dierent
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 dier between dierent versions of
the ICU library compiled with PHP and also based on the fact
whether the PHP intl extension
2 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 environments and verify that the version of the ICU library is the same.
See also: Setting up your PHP environment for internationalization.
Note also that even if the intl extension is installed, formatting
date and time values for years >=2038 or <=1901 on 32bit
systems will fall back to the PHP implementation, which does
not provide localized month and day names, because intl uses
a 32bit UNIX timestamp internally. On a 64bit system the intl
formatter is used in all cases if installed.
8.1.1 Conguring the formatter
The default formats used by the formatter methods can be adjusted using the
properties of the
formatter class.
wide by conguring the
formatter
You can adjust these values application
component in your application cong. An
example conguration is shown in the following. For more details about the
available properties check out the
class
API documentation of the Formatter
and the following subsections.
'components' => [
'formatter' => [
1
2
http://php.net/manual/en/book.intl.php
http://php.net/manual/en/book.intl.php
8.1.
],
DATA FORMATTER
313
'dateFormat' => 'dd.MM.yyyy',
'decimalSeparator' => ',',
'thousandSeparator' => ' ',
'currencyCode' => 'EUR',
],
8.1.2 Formatting Date and Time values
The formatter class provides dierent methods for formatting date and time
values. These are:
• date - the value is formatted as a date e.g. January 01, 2014.
• time - the value is formatted as a time e.g. 14:23.
• datetime - the value is formatted as date and time e.g. January 01,
2014 14:23.
• timestamp - the value is formatted as a unix timestamp3 e.g. 1412609982
.
• relativeTime
- the value is formatted as the time interval between a
date and now in human readable form e.g.
The date and time format for the
date, time,
1 hour ago.
and
datetime methods can
$dateFormat,
be specied globally by conguring the formatters properties
$timeFormat,
and
$datetimeFormat.
By default the formatter uses a shortcut format that is interpreted differently according to the currently active locale so that dates and times are
formatted in a way that is common for the users country and language.
There are four dierent shortcut formats available:
• short in en_GB locale will print for example 06/10/2014 for date and 15:58
for time, while
• medium will print 6 Oct 2014 and 15:58:42,
• long will print 6 October 2014 and 15:58:42 GMT,
• and full will print Monday, 6 October 2014 and 15:58:42 GMT.
Additionally you can specify custom formats using the syntax dened by the
ICU Project
URL:
4 which is described in the ICU manual under the following
http://userguide.icu-project.org/formatparse/datetime.
Al-
5
ternatively you can use the syntax that can be recognized by the PHP date()
function using a string that is prexed with
php:.
// ICU format
echo Yii::$app->formatter->asDate('now', 'yyyy-MM-dd'); // 2014-10-06
// PHP date()-format
echo Yii::$app->formatter->asDate('now', 'php:Y-m-d'); // 2014-10-06
3
http://en.wikipedia.org/wiki/Unix_time
http://site.icu-project.org/
5
http://php.net/manual/en/function.date.php
4
314
CHAPTER 8.
DISPLAYING DATA
Time zones
When formatting date and time values, Yii will convert them to the
time zone.
configured
Therefore the input value is assumed to be in UTC unless a time
zone is explicitly given. For this reason it is recommended to store all date
and time values in UTC, preferably as a UNIX timestamp, which is always
UTC by denition. If the input value is in a time zone dierent from UTC,
the time zone has to be stated explicitly like in the following example:
// assuming Yii::$app->timeZone = 'Europe/Berlin';
echo Yii::$app->formatter->asTime(1412599260); // 14:41:00
echo Yii::$app->formatter->asTime('2014-10-06 12:41:00'); // 14:41:00
echo Yii::$app->formatter->asTime('2014-10-06 14:41:00 CEST'); // 14:41:00
Since version 2.0.1 it is also possible to congure the time zone that is assumed for timestamps that do not include a time zone identier like the
second example in the code above.
$defaultTimeZone
You can set
yii\i18n\Formatter::
to the time zone you use for data storage.
Note: As time zones are subject to rules made by the governments around the world and may change frequently, it is likely
that you do not have the latest information in the time zone
database installed on your system.
You may refer to the ICU
6
manual for details on updating the time zone database.
See
also: Setting up your PHP environment for internationalization.
8.1.3 Formatting Numbers
For formatting numeric values the formatter class provides the following
methods:
• integer - the value is formatted as an integer e.g. 42.
• decimal - the value is formatted as a decimal number considering decimal and thousand separators e.g.
2,542.123
or
2.542,123.
• percent - the value is formatted as a percent number e.g. 42%.
• scientific - the value is formatted as a number in scientic format
e.g.
4.2E4.
• currency
.
- the value is formatted as a currency value e.g.
£420.00
Note that for this function to work properly, the locale needs to
include a country part e.g.
en_GB
or
en_US
because language only would
be ambiguous in this case.
• size
- the value that is a number of bytes is formatted as a human
readable size e.g.
• shortSize
410 kibibytes.
- is the short version of
size,
e.g.
410 KiB.
6
http://userguide.icu-project.org/datetime/timezone#
TOC-Updating-the-Time-Zone-Data
8.1.
DATA FORMATTER
315
The format for number formatting can be adjusted using the
and
thousandSeparator
decimalSeparator
which are set by default according to the locale.
yii\i18n\Formatter::$numberFormatterOptions
yii\i18n\Formatter::$numberFormatterTextOptions can be used to
For more advanced conguration,
and
7
congure the internally used NumberFormatter class
For example, to adjust the maximum and minimum value of fraction
digits, you can congure
yii\i18n\Formatter::$numberFormatterOptions
property like the following:
'numberFormatterOptions' => [
NumberFormatter::MIN_FRACTION_DIGITS => 0,
NumberFormatter::MAX_FRACTION_DIGITS => 2,
]
8.1.4 Other formatters
In addition to date, time and number formatting, Yii provides a set of other
useful formatters for dierent situations:
• raw
- the value is outputted as is, this is a pseudo-formatter that has
no eect except that
null
values will be formatted using
• text - the value is HTML-encoded.
nullDisplay.
This is the default format used by
the GridView DataColumn.
• ntext
- the value is formatted as an HTML-encoded plain text with
newlines converted into line breaks.
• paragraphs - the value is formatted as HTML-encoded text paragraphs
wrapped into
• html
<p>
tags.
- the value is puried using
HtmlPurifier
You can pass additional options such as
to avoid XSS attacks.
['html', ['Attr.AllowedFrameTargets
' => ['_blank']]].
•
•
•
•
email - the value is formatted as a mailto-link.
image - the value is formatted as an image tag.
url - the value is formatted as a hyperlink.
boolean - the value is formatted as a boolean.
rendered as
Yes
and
false
as
No,
language. You can adjust this by conguring the
::$booleanFormat
8.1.5
true
is
yii\i18n\Formatter
property.
null-values
For values that are
null
in PHP, the formatter class will print a placeholder
instead of an empty string which defaults to
(not set)
current application language. You can congure the
to set a custom placeholder.
values, you can set
7
By default
translated to the current application
translated to the
nullDisplay
property
If you do not want special handling for
nullDisplay
to
null.
http://php.net/manual/en/class.numberformatter.php
null
316
CHAPTER 8.
DISPLAYING DATA
8.2 Pagination
When there's too much data to be displayed on a single page at once it's
often divided into parts each containing some data items and displayed one
part at a time. Such parts are called pages thus the name pagination.
If you're using data provider with one of the data widgets pagination is
yii\data
\Pagination object, ll it with data such as total item count, page size
and current page, apply it to the query and then feed it to link pager.
already sorted out for you automatically. If not, you need to create
First of all in controller action we're creating pagination object and lling
it with data:
function actionIndex()
{
$query = Article::find()->where(['status' => 1]);
$countQuery = clone $query;
$pages = new Pagination(['totalCount' => $countQuery->count()]);
$models = $query->offset($pages->offset)
->limit($pages->limit)
->all();
}
return $this->render('index', [
'models' => $models,
'pages' => $pages,
]);
Then in a view we're outputting models for the current page and passing
pagination object to the link pager:
foreach ($models as $model) {
// display $model here
}
// display pagination
echo LinkPager::widget([
'pagination' => $pages,
]);
8.3 Sorting
Sometimes the data that is to be displayed should be sorted according to
one or several attributes. If you are using a data provider with one of the
data widgets, sorting is handled for you automatically. If not, you should
create a
yii\data\Sort
instance, congure it and apply it to the query. It
can also be passed to the view, where it can be used to create links to sort
by certain attributes.
A typical usage example is as follows,
function actionIndex()
8.4.
{
DATA PROVIDERS
317
$sort = new 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',
],
],
]);
$models = Article::find()
->where(['status' => 1])
->orderBy($sort->orders)
->all();
}
return $this->render('index', [
'models' => $models,
'sort' => $sort,
]);
In the view:
// display links leading to sort actions
echo $sort->link('name') . ' | ' . $sort->link('age');
foreach ($models as $model) {
// display $model here
}
In the above, we declare two attributes that support sorting:
name
and
age.
We pass the sort information to the Article query so that the query results are
sorted by the orders specied by the Sort object. In the view, we show two
hyperlinks that can lead to pages with the data sorted by the corresponding
attributes.
The
Sort
class will obtain the parameters passed with the request au-
tomatically and adjust the sort options accordingly.
parameters by conguring the
$params
You can adjust the
property.
8.4 Data providers
Note: This section is under development.
Data provider abstracts data set via
yii\data\DataProviderInterface and
handles pagination and sorting. It can be used by grids, lists and other data
widgets.
318
CHAPTER 8.
DISPLAYING DATA
yii\data\ActiveDataProvider,
yii\data\SqlDataProvider.
In Yii there are three built-in data providers:
yii\data\ArrayDataProvider
and
8.4.1 Active data provider
ActiveDataProvider
\Query
and
provides data by performing DB queries using
yii\db\ActiveQuery.
yii\db
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.4.2 Array data provider
ArrayDataProvider implements a data provider based on a data array.
The
yii\data\ArrayDataProvider::$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 congure the
yii\data\ArrayDataProvider::$sort and yii\data\ArrayDataProvider
::$pagination properties to customize the sorting and pagination behaviors.
Elements in the
yii\data\ArrayDataProvider::$allModels array may
be either objects (e.g.
model objects) or associative arrays (e.g.
query
8.4.
DATA PROVIDERS
results of DAO). Make sure to set the
319
yii\data\ArrayDataProvider::$key
property to the name of the eld that uniquely identies a data record or
false if you do not have such a eld.
Compared to
ActiveDataProvider, ArrayDataProvider
because it needs to have
could be less ecient
yii\data\ArrayDataProvider::$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 congure
the
sort property so that the provider knows which columns can
be sorted.
8.4.3 SQL data provider
SqlDataProvider implements a data provider based on a plain SQL statement. 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 yii\data\SqlDataProvider::
$sql statement with ORDER BY and LIMIT clauses. You may congure
the yii\data\SqlDataProvider::$sort and yii\data\SqlDataProvider
::$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();
$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
],
320
CHAPTER 8.
SORT_DESC],
],
]);
],
DISPLAYING DATA
'desc' => ['first_name' => SORT_DESC, 'last_name' =>
'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 congure the
yii\data\SqlDataProvider::$totalCount
property
to be the total number of rows (without pagination). And if you
want to use the sorting feature, you must congure the
\SqlDataProvider::$sort
yii\data
property so that the provider knows
which columns can be sorted.
8.4.4 Implementing your own custom data provider
Yii allows you to introduce your own custom data providers. In order to do
it you need to implement the following
protected
methods:
• prepareModels that prepares the data models that will be made available
in the current page and returns them as an array.
• prepareKeys
that accepts an array of currently available data models
and returns keys associated with them.
• prepareTotalCount
that returns a value indicating the total number of
data models in the data provider.
Below is an example of a data provider that reads CSV eciently:
<?php
class CsvDataProvider extends \yii\data\BaseDataProvider
{
/**
* @var string name of the file to read
*/
public $filename;
/**
* @var string|callable name of the key column or a callable returning
it
*/
public $key;
/**
* @var SplFileObject
8.4.
DATA PROVIDERS
321
*/
protected $fileObject; // SplFileObject is very convenient for seeking
to particular line in a file
/**
* @inheritdoc
*/
public function init()
{
parent::init();
}
// open file
$this->fileObject = new SplFileObject($this->filename);
/**
* @inheritdoc
*/
protected function prepareModels()
{
$models = [];
$pagination = $this->getPagination();
if ($pagination === false) {
// in case there's no pagination, read all lines
while (!$this->fileObject->eof()) {
$models[] = $this->fileObject->fgetcsv();
$this->fileObject->next();
}
} else {
// in case there's pagination, read only a single page
$pagination->totalCount = $this->getTotalCount();
$this->fileObject->seek($pagination->getOffset());
$limit = $pagination->getLimit();
}
}
for ($count = 0; $count < $limit; ++$count) {
$models[] = $this->fileObject->fgetcsv();
$this->fileObject->next();
}
return $models;
/**
* @inheritdoc
*/
protected function prepareKeys($models)
{
if ($this->key !== null) {
$keys = [];
foreach ($models as $model) {
322
CHAPTER 8.
}
}
DISPLAYING DATA
if (is_string($this->key)) {
$keys[] = $model[$this->key];
} else {
$keys[] = call_user_func($this->key, $model);
}
return $keys;
} else {
return array_keys($models);
}
/**
* @inheritdoc
*/
protected function prepareTotalCount()
{
$count = 0;
while (!$this->fileObject->eof()) {
$this->fileObject->next();
++$count;
}
}
}
return $count;
8.5 Data widgets
Yii provides a set of widgets that can be used to display data. While the
DetailView widget can be used to display data for a single record, ListView
and GridView can be used to display a list or table of data records providing
features like pagination, sorting and ltering.
8.5.1 DetailView
The
DetailView
widget displays the details of a single data
model.
It is best used for displaying a model in a regular format (e.g.
each
model attribute is displayed as a row in a table). The model can be either
an instance or subclass of
yii\base\Model
such as an active record or an
associative array.
DetailView uses the
$attributes
property to determine which model
attributes should be displayed and how they should be formatted. See the
formatter section for available formatting options.
A typical usage of DetailView is as follows:
echo DetailView::widget([
8.5.
DATA WIDGETS
323
'model' => $model,
'attributes' => [
'title',
// title attribute (in plain text)
'description:html',
// description attribute formatted as HTML
[
// the owner name of the model
'label' => 'Owner',
'value' => $model->owner->name,
],
'created_at:datetime', // creation date formatted as datetime
],
]);
8.5.2 ListView
ListView widget is used to display data from a data provider. Each data
model is rendered using the specied view file. Since it provides features
The
such as pagination, sorting and ltering out of the box, it is handy both to
display information to end user and to create data managing UI.
A typical usage is as follows:
use yii\widgets\ListView;
use yii\data\ActiveDataProvider;
$dataProvider = new ActiveDataProvider([
'query' => Post::find(),
'pagination' => [
'pageSize' => 20,
],
]);
echo ListView::widget([
'dataProvider' => $dataProvider,
'itemView' => '_post',
]);
The
_post
view le could contain the following:
<?php
use yii\helpers\Html;
use yii\helpers\HtmlPurifier;
?>
<div class="post">
<h2><?= Html::encode($model->title) ?></h2>
<?= HtmlPurifier::process($model->text) ?>
</div>
In the view le above, the current data model is available as
$model.
Addi-
tionally the following variables are available:
• $key: mixed, the key value associated with the data item.
• $index: integer, the zero-based index of the data item in the items array
returned by the data provider.
• $widget:
ListView, this widget instance.
324
CHAPTER 8.
DISPLAYING DATA
If you need to pass additional data to each view, you can use the
$viewParams
property to pass key value pairs like the following:
echo ListView::widget([
'dataProvider' => $dataProvider,
'itemView' => '_post',
'viewParams' => [
'fullView' => true,
'context' => 'main-page',
// ...
],
]);
These are then also available as variables in the view.
8.5.3 GridView
Data grid or GridView is one of the most powerful Yii widgets. It is extremely
useful if you need to quickly build the admin section of the system. It takes
data from a data provider and renders each row using a set of
columns
presenting data in the 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 expressions of attributes or static text).
The minimal code needed to use GridView is as follows:
use yii\grid\GridView;
use yii\data\ActiveDataProvider;
$dataProvider = new ActiveDataProvider([
'query' => Post::find(),
'pagination' => [
'pageSize' => 20,
],
]);
echo GridView::widget([
'dataProvider' => $dataProvider,
]);
The above code rst creates a data provider and then uses GridView to
display every attribute in every row taken from the data provider.
The
displayed table is equipped with sorting and pagination functionality out of
the box.
Grid columns
yii\grid\Column
columns property of GridView congura-
The columns of the grid table are congured in terms of
classes, which are congured in the
tion. Depending on column type and settings these are able to present data
dierently. The default class is
yii\grid\DataColumn,
model attribute and can be sorted and ltered by.
which represents a
8.5.
DATA WIDGETS
325
echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
// Simple columns defined by the data contained in $dataProvider.
// Data from the model's column will be used.
'id',
'username',
// More complex one.
[
'class' => 'yii\grid\DataColumn', // can be omitted, as it is
the default
'value' => function ($data) {
return $data->name; // $data['name'] for array data, e.g.
using SqlDataProvider.
},
],
],
]);
Note that if the
columns
part of the conguration isn't specied, Yii tries
to show all possible columns of the data provider's model.
Column classes
Grid columns could be customized by using dierent column classes:
echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
[
'class' => 'yii\grid\SerialColumn', // <-- here
// you may configure additional properties here
],
In addition to column classes provided by Yii that we'll review below, you
can create your own column classes.
Each column class extends from
yii\grid\Column so that there are some
common options you can set while conguring grid columns.
•
•
•
•
header allows to set content for header row.
footer allows to set content for footer row.
visible denes if the column should be visible.
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 by passing arrays to:
• headerOptions
• footerOptions
326
CHAPTER 8.
DISPLAYING DATA
• filterOptions
• contentOptions
Data column
Data column
is used for displaying and sorting data. It is
the default column type so the specifying class could be omitted when using
it.
The main setting of the data column is its
ues correspond to methods in the
Formatter
formatter
format
property.
Its val-
application component that is
by default:
echo GridView::widget([
'columns' => [
[
'attribute'
'format' =>
],
[
'attribute'
'format' =>
],
],
]);
In the above,
text
=> 'name',
'text'
=> 'birthday',
['date', 'php:Y-m-d']
corresponds to
yii\i18n\Formatter::asText().
The
value of the column is passed as the rst argument. In the second column
denition,
date
corresponds to
yii\i18n\Formatter::asDate().
The value
of the column is, again, passed as the rst argument while `php:Y-m-d' is
used as the second argument value.
For a list of available formatters see the section about Data Formatting.
For conguring data columns there is also a shortcut format which is
described in the API documentation for
Action column Action column
columns.
displays action buttons such as update
or delete for each row.
echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
[
'class' => 'yii\grid\ActionColumn',
// you may configure additional properties here
],
Available properties you can congure are:
• controller
is the ID of the controller that should handle the actions.
If not set, it will use the currently active controller.
• template
denes 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
8.5.
DATA WIDGETS
327
column). They will be replaced by the corresponding button rendering
callbacks specied in
buttons.
For example, the token
replaced by the result of the callback
buttons['view'].
{view}
will be
If a callback
cannot be found, the token will be replaced with an empty string. The
default tokens are
• buttons
{view} {update} {delete}.
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, $key) {
// return the button HTML code
}
In the code above,
$url
is the URL that the column creates for the
$model is the model object being rendered for
$key is the key of the model in the data provider
button,
the current row,
and
array.
• urlCreator is a callback that creates a button URL using the specied
model information. The signature of the callback should be the same
yii\grid\ActionColumn::createUrl(). If this property is
not set, button URLs will be created using yii\grid\ActionColumn
::createUrl().
as that of
Checkbox column Checkbox column displays a column of checkboxes.
To add a CheckboxColumn to the GridView, add it to the
columns
con-
guration as follows:
echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
// ...
[
'class' => 'yii\grid\CheckboxColumn',
// you may configure additional properties here
],
],
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
going forward.
Usage is as simple as the following:
echo GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
1
and
328
CHAPTER 8.
DISPLAYING DATA
['class' => 'yii\grid\SerialColumn'], // <-- here
// ...
Sorting data
Note: This section is under development.
• https://github.com/yiisoft/yii2/issues/1576
Filtering data
For ltering data the GridView needs a model that takes the input from,
the ltering 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).
provides a
This class denes the validation rules for the search and
search()
method that will return the data provider.
To add the search capability for the
Post
model, we can create
like the following example:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
class PostSearch extends Post
{
public function rules()
{
// only fields in rules() are searchable
return [
[['id'], 'integer'],
[['title', 'creation_date'], 'safe'],
];
}
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,
PostSearch
8.5.
DATA WIDGETS
329
]);
// 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->title])
->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,
'columns' => [
// ...
],
]);
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 author's name
instead of just his
id.
You do this by dening the attribute name in
\GridView::$columns
named
author
as
author.name
when the
Post
yii\grid
model has a relation
and the author model has an attribute
name.
The GridView
will then display the name of the author but sorting and ltering 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:
330
CHAPTER 8.
DISPLAYING DATA
$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 dene 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 lter 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
dier, you have to pay attention to where you use the alias and
where you 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
and the relation name in all other denitions such as
and
rules()
attributes()
etc.
For example, if 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']); }]);
8.5.
DATA WIDGETS
331
It is also possible to just call
$query->joinWith(['author']);
when
the alias is dened in the relation denition.
The alias has to be used in the lter condition but the attribute
name stays the same:
$query->andFilterWhere(['LIKE', 'au.name', $this->getAttribute('
author.name')]);
The same is true for the sorting denition:
$dataProvider->sort->attributes['author.name'] = [
'asc' => ['au.name' => SORT_ASC],
'desc' => ['au.name' => SORT_DESC],
];
Also, when specifying the
defaultOrder for sorting, 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 joining with
relations.
Using SQL views for ltering, sorting and displaying data
There
is also another approach that can be faster and more useful - SQL views.
For example, if we need to show the gridview with users and their proles,
we can do so 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 the ActiveRecord that will be representing this
view:
namespace app\models\views\grid;
use yii\db\ActiveRecord;
class UserView extends ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'vw_user_info';
}
332
CHAPTER 8.
DISPLAYING DATA
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 use this UserView active record with search models, without additional specication of sorting and ltering attributes. All attributes
will be working out of the box. Note that this approach has several pros and
cons:
•
you don't need to specify dierent sorting and ltering conditions.
Everything works out of the box;
•
it can be much faster because of the data size, count of sql queries
performed (for each relation you will not need any additional query);
•
since this is just a simple mapping UI on the sql view it lacks some
domain logic that is in your entities, so if you have some methods like
isActive, isDeleted
or others that will inuence the UI, you will need
to duplicate them in this class too.
Multiple GridViews on one page
You can use more than one GridView on a single page but some additional
conguration is needed so that they do not interfere with each other. When
using multiple instances of GridView you have to congure dierent parameter names for the generated sort and pagination links so that each GridView
has its own individual sorting and pagination.
You do so by setting the
8.6.
WORKING WITH CLIENT SCRIPTS
sortParam
and
pageParam
of the dataProvider's
333
sort
and
pagination
in-
stances.
Assume we want to list the
Post
User models for which we have
$userProvider and $postProvider:
and
already prepared two data providers in
use yii\grid\GridView;
$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
Note: This section is under development.
TBD
8.6 Working with Client Scripts
Note: This section is under development.
Registering scripts
yii\web\View object you can register scripts. There are two dedicated methods for it: registerJs() for inline scripts and registerJsFile()
With the
for external scripts. Inline scripts are useful for conguration 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');
The rst 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:
• View::POS_HEAD for head section.
• View::POS_BEGIN for right after opening <body>.
• View::POS_END for right before closing </body>.
334
CHAPTER 8.
DISPLAYING DATA
• View::POS_READY for executing code on document ready event. This
will register jQuery automatically.
• View::POS_LOAD for executing code on document load event. This will
register jQuery 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' => [\yii\
web\JqueryAsset::className()]]);
The arguments for
registerJsFile() are similar to those for registerCssFile().
In the above example, we register the
JqueryAsset.
main.js
This means the
main.js
le with the dependency on
le will be added AFTER
jquery.js.
main.js
Without this dependency specication, the relative order between
and
jquery.js
would be undened.
registerCssFile(), it is also highly recommended that you use
asset bundles to register external JS les rather than using registerJsFile().
Like for
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 dene asset
bundles in asset manager section of the guide. As for using already dened
asset bundle, it's very straightforward:
\frontend\assets\AppAsset::register($this);
Registering CSS
You can register CSS using
registerCss()
or
registerCssFile().
The
former registers a block of CSS code while the latter registers an external
CSS le. For example,
$this->registerCss("body { background: #f00; }");
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.
8.7.
THEMING
335
$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 le to the head section of the page.
•
•
The rst argument species the CSS le to be registered.
The second argument species the HTML attributes for the resulting
<link>
tag. The option
depends
is specially handled. It species which
asset bundles this CSS le depends on. In this case, the dependent asset
bundle is
the CSS
•
BootstrapAsset. This means the CSS le will be added after
les in BootstrapAsset.
The last argument species an ID identifying this CSS le. If it is not
provided, the URL of the CSS le will be used instead.
It is highly recommended that you use asset bundles to register external
CSS les rather than using
registerCssFile().
Using asset bundles allows
you to combine and compress multiple CSS les, which is desirable for high
trac websites.
8.7 Theming
Note: This section is under development.
A theme is a directory of view and layout les. Each le of the theme overrides corresponding le of an application when rendered. A single application
may use multiple themes and each may provide totally dierent experiences.
At any time only one theme can be active.
Note: Themes are usually not meant to be redistributed since
views are too application specic. If you want to redistribute a
customized look and feel, consider CSS and JavaScript les in
the form of asset bundles instead.
8.7.1 Conguring a theme
Theme conguration is specied via the
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 cong le:
'components' => [
'view' => [
'theme' => [
'pathMap' => ['@app/views' => '@app/themes/basic'],
'baseUrl' => '@web/themes/basic',
],
],
],
336
CHAPTER 8.
In the above,
baseUrl
pathMap
DISPLAYING DATA
denes a map of original paths to themed paths while
denes the base URL for resources referenced by theme les.
pathMap is ['@app/views' => '@app/themes/basic']. That means
view in @app/views will be rst searched under @app/themes/basic
In our case,
that every
and if a view exists in the theme directory it will be used instead of the
original view.
For example, with a conguration above a themed version of a view le
@app/views/site/index.php will be @app/themes/basic/site/index.php. It basically replaces @app/views in @app/views/site/index.php with @app/themes/basic.
In order to congure a theme at runtime, you can use the following code
before rendering a view. Typically, it will be placed in a controller:
$this->getView()->theme = Yii::createObject([
'class' => '\yii\base\Theme',
'pathMap' => ['@app/views' => '@app/themes/basic'],
'baseUrl' => '@web/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', // <-- !!!
],
],
],
],
@app/modules/blog/views/comment/index.php with @app
/themes/basic/modules/blog/views/comment/index.php.
It will allow you to theme
Theming widgets
In order to theme a widget view located at
.php,
@app/widgets/currency/views/index
you need the following conguration for the view component theme:
'components' => [
'view' => [
'theme' => [
'pathMap' => ['@app/widgets' => '@app/themes/basic/widgets'],
],
],
],
@app/
@app/themes/basic/widgets/currency/index.
With the conguration above you can create a themed version of the
widgets/currency/index.php
php.
view in
8.7.
THEMING
337
8.7.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',
],
]
@app/themes/christmas/site/
@app/themes/basic/site/index.php.
In this case, rst the view will be searched for in
index.php
then if it's not found it will check
If there's no view there as well, then the application view will be used.
This ability is especially useful if you want to temporarily or conditionally
override some views.
338
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 login process. Typically, authentication uses the combination of an identiera
username or email addressand a password. The user submits these values
through a form, and the application then compares the submitted information against that previously stored (e.g., upon registration).
In Yii, this entire process is performed semi-automatically, leaving the
developer to merely implement
yii\web\IdentityInterface,
portant class in the authentication system.
IdentityInterface
is accomplished using the
the most im-
Typically, implementation of
User
model.
You can nd a fully featured example of authentication in the advanced
1
project 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);
}
1
https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/
README.md
339
340
CHAPTER 9.
SECURITY
/**
* Finds an identity by the given token.
*
* @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
getId
getAuthKey and
ID value and returns a model instance associated with that ID. The
method returns the ID itself.
Two of the other methods
validateAuthKey are used to provide
cookie. The getAuthKey method should
user.
extra security to the remember me
return a string that is unique for each
You can reliably create a unique string using
()->generateRandomString().
Yii::$app->getSecurity
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
();
9.2.
AUTHORIZATION
341
}
return true;
}
return false;
}
The
validateAuthKey
method just needs to compare the
$authKey
variable,
passed as a parameter (itself retrieved from a cookie), with the value fetched
from the 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 lter that can be attached to a controller or a
module as a behavior. ACF will check a set of
access rules
to make sure
the current user can access the requested action.
The code below shows how to use ACF which is implemented as
\filters\AccessControl:
use yii\filters\AccessControl;
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' => ['@'],
],
],
yii
342
CHAPTER 9.
];
}
SECURITY
],
}
// ...
In the code above ACF is attached to the
site
controller as a behavior. This
only option species that the
login, logout and signup actions. The rules
is the typical way of using an action lter. The
ACF should only be applied to
option species the
•
access rules,
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 nds 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
yii\web\User::loginRequired(),
which may redirect the browser to the login page.
•
::$denyCallback
[
]
yii\web\ForbiddenHttpException.
yii\filters\AccessControl
If the user is already authenticated, it will throw a
You may customize this behavior by conguring the
property:
'class' => AccessControl::className(),
'denyCallback' => function ($rule, $action) {
throw new \Exception('You are not allowed to access this page');
}
Access rules support many options. Below is a summary of the supported
options. You may also extend yii\filters\AccessRule to create your own
customized access rule classes.
• allow: species whether this is an allow or deny rule.
• actions: species 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.
• controllers:
species 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.
• roles:
species which user roles that this rule matches.
cial roles are recognized, and they are checked via
$isGuest:
Two spe-
yii\web\User::
9.2.
AUTHORIZATION
?:
@:
343
matches a guest user (not authenticated yet)
matches an authenticated user
Using other role names requires RBAC (to be described in the next
section), and
yii\web\User::can()
will be called.
If this option is
empty or not set, it means this rule applies to all roles.
• ips:
species which
client IP addresses
address can contain the wildcard
*
this rule matches. An IP
at the end so that it matches IP
addresses with the same prex. 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.
• verbs:
species which request method (e.g.
GET, POST) this rule matches.
The comparison is case-insensitive.
• matchCallback:
species a PHP callable that should be called to de-
termine if this rule should be applied.
• denyCallback:
species 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 yii\filters\AccessControl;
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';
}
],
],
],
];
}
}
// Match callback called! This page can be accessed only each October 31
st
public function actionSpecialCallback()
{
return $this->render('happy-halloween');
}
344
CHAPTER 9.
SECURITY
9.2.2 Role based access control (RBAC)
Role-Based Access Control (RBAC) provides a simple yet powerful central-
2 for details about compar-
ized access control. Please refer to the Wikipedia
ing RBAC with other more traditional access control schemes.
Yii implements a General Hierarchical RBAC, following the NIST RBAC
3
model . It provides the RBAC functionality through the
authManager
ap-
plication component.
Using RBAC involves two parts of work. The rst 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 rst 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 specied 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 determine 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
the more special
tree
partial order
hierarchy which includes
hierarchy. While a role can contain a permission, it is
not true vice versa.
Conguring RBAC Manager
Before we set o to dene authorization data and perform access checking,
authManager application component. Yii provides
authorization managers: yii\rbac\PhpManager and yii\rbac
we need to congure the
two types of
\DbManager.
The former uses a PHP script le to store authorization data,
while the latter stores authorization data in a database. You may consider
using the former if your application does not require very dynamic role and
permission management.
2
3
http://en.wikipedia.org/wiki/Role-based_access_control
http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf
9.2.
AUTHORIZATION
345
conguring authManager with
how to congure the
authManager
yii\rbac\PhpManager
PhpManager
The following code shows
in the application conguration using the
class:
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
],
// ...
],
];
The
authManager
Tip:
can now be accessed via
By default,
les under
\Yii::$app->authManager.
yii\rbac\PhpManager
@app/rbac/
stores RBAC data in
directory. Make sure the directory and all
the les in it are writable by the Web server process if permissions
hierarchy needs to be changed online.
conguring authManager with DbManager
to congure the
authManager
\rbac\DbManager
The following code shows how
in the application conguration using the
yii
class:
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
],
// ...
],
];
DbManager
uses four database tables to store its data:
• itemTable:
the table for storing authorization items.
Defaults to
auth_item.
• itemChildTable:
the table for storing authorization item hierarchy.
Defaults to auth_item_child.
• assignmentTable:
the table for storing authorization item assign-
ments. Defaults to auth_assignment.
• ruleTable:
the table for storing rules. Defaults to auth_rule.
Before you can go on you need to create those tables in the database. To do
@yii/rbac/migrations:
yii migrate --migrationPath=@yii/rbac/migrations
The authManager can now be accessed via \Yii::$app->authManager.
this, you can use the migration stored in
Building Authorization Data
Building authorization data is all about the following tasks:
346
CHAPTER 9.
•
•
•
•
•
SECURITY
dening roles and permissions;
establishing relations among roles and permissions;
dening rules;
associating rules with roles and permissions;
assigning roles to users.
Depending on authorization exibility requirements the tasks above could
be done in dierent ways.
If your permissions hierarchy doesn't change at all and you have a xed
number of users you can create a console command that will initialize authorization data once via APIs oered by
authManager:
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
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);
}
9.2.
AUTHORIZATION
After executing the command with
erarchy:
347
yii rbac/init
we'll get the following hi-
348
CHAPTER 9.
SECURITY
Author can create post, admin can update post and do everything author
can.
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 in your advanced project template you need to modify
models\SignupForm::signup()
frontend\
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 updated authorization data, special user interfaces (i.e. admin panel) may need
to be developed using APIs oered by
authManager.
Using Rules
As aforementioned, rules add additional constraint to roles and permissions.
A rule is a class extending from
execute() method.
yii\rbac\Rule.
It must implement the
In the hierarchy we've created previously author cannot
edit his own post. Let's x it. First we need a rule to verify that the user is
the post author:
namespace app\rbac;
use yii\rbac\Rule;
/**
* Checks if authorID matches user passed via params
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
9.2.
}
AUTHORIZATION
349
* @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
permission
updateOwnPost
post
is created by
$user.
We'll create a special
in the command we've used previously:
$auth = Yii::$app->authManager;
// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $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:
350
CHAPTER 9.
SECURITY
Access Check
With the authorization data ready, access check is as simple as a call to
the
yii\rbac\ManagerInterface::checkAccess()
method. Because most
access check is about the current user, for convenience Yii provides a shortcut
method
yii\web\User::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
to get to
Jane:
createPost
and trying
9.2.
AUTHORIZATION
351
In order to check if user can update post we need to pass an extra parameter 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:
352
CHAPTER 9.
SECURITY
updatePost and going through updateOwnPost. In
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
We're starting with the
order to pass it
John.
In case of Jane it is a bit simpler since she's an admin:
9.2.
AUTHORIZATION
353
Using Default Roles
A default role is a role that is
implicitly
\rbac\ManagerInterface::assign()
assigned to
all
users. The call to
yii
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
354
CHAPTER 9.
SECURITY
groups, respectively. You can set up the RBAC data as follows,
namespace app\rbac;
use Yii;
use yii\rbac\Rule;
/**
* 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;
}
$auth = Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$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()
this hierarchy as well.
execute()
method of the rule class, you need to respect
That is why when the role name is author, the
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, congure
::$defaultRoles:
return [
// ...
authManager by listing the two roles in yii\rbac\BaseManager
9.3.
WORKING WITH PASSWORDS
355
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'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
group value is 2, the author role would
would apply to the user; and if the
apply.
9.3 Working with Passwords
Note: This section is under development.
Good security is vital to the health and success of any application. Unfortunately, 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
sucient, 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.
bcrypt hash using the crypt function4 . Yii
which make using crypt to securely generate
In PHP, you can create a
provides two helper functions
and verify hashes easier.
When a user provides a password for the rst time (e.g., upon registration), 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.
4
http://php.net/manual/en/function.crypt.php
356
CHAPTER 9.
SECURITY
When a user attempts to log in, the submitted password must be veried
against the previously hashed and stored password:
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()->encryptByPassword($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()->decryptByPassword($encryptedData,
$secretKey);
9.3.4 Conrming 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 conrm data integrity in the form of two helper functions.
Prex the data with a hash generated from the secret key and data
9.3.
WORKING WITH PASSWORDS
357
// $secretKey our application or user secret, $genuineData obtained from a
reliable source
$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey);
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 app\controllers;
use yii\web\Controller;
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 app\controllers;
use yii\web\Controller;
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
358
CHAPTER 9.
SECURITY
Error: not existing le: https://github.com/yiisoft/yii2-authclient/blob/master/d
9.4.
SECURITY BEST PRACTICES
359
9.4 Security best practices
Below we'll review common security principles and describe how to avoid
threats when developing applications using Yii.
9.4.1 Basic principles
There are two main principles when it comes to security no matter which
application is being developed:
1. Filter input.
2. Escape output.
Filter input
Filter input means that input should never be considered safe and you should
always check if the value you've got is actually among allowed ones. For example, if we know that sorting could be done by three elds
and
status
title, created_at
and the eld could be supplied via user input, it's better to check
the value we've got right where we're receiving it. In terms of basic PHP
that would look like the following:
$sortBy = $_GET['sort'];
if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
throw new Exception('Invalid sort value.');
}
In Yii, most probably you'll use form validation to do alike checks.
Escape output
Escape output means that depending on context where we're using data it
should be escaped i.e. in context of HTML you should escape
<, >
and alike
special characters. In context of JavaScript or SQL it will be dierent set of
characters. Since it's error-prone to escape everything manually Yii provides
various tools to perform escaping for dierent contexts.
9.4.2 Avoiding SQL injections
SQL injection happens when query text is formed by concatenating unescaped strings such as the following:
$username = $_GET['username'];
$sql = "SELECT * FROM user WHERE username = '$username'";
Instead of supplying correct username attacker could give your applications
something like
'; DROP TABLE user; --.
Resulting SQL will be the following:
SELECT * FROM user WHERE username = ''; DROP TABLE user; --'
360
CHAPTER 9.
SECURITY
This is valid query that will search for users with empty username and then
will drop
user
table most probably resulting in broken website and data loss
(you've set up regular backups, right?).
In Yii most of database querying happens via Active Record which properly uses PDO prepared statements internally. In case of prepared statements
it's not possible to manipulate query as was demonstrated above.
Still, sometimes you need raw queries or query builder. In this case you
should use safe ways of passing data. If data is used for column values it's
preferred to use prepared statements:
// query builder
$userIDs = (new Query())
->select('id')
->from('user')
->where('status=:status', [':status' => $status])
->all();
// DAO
$userIDs = $connection
->createCommand('SELECT id FROM user where status=:status')
->bindValues([':status' => $status])
->queryColumn();
If data is used to specify column names or table names the best thing to do
is to allow only predened set of values:
function actionList($orderBy = null)
{
if (!in_array($orderBy, ['name', 'status'])) {
throw new BadRequestHttpException('Only name and status are allowed
to order by.')
}
}
// ...
In case it's not possible, table and column names should be escaped. Yii has
special syntax for such escaping which allows doing it the same way for all
databases it supports:
$sql = "SELECT COUNT([[$column]]) FROM {{table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();
You can get details about the syntax in Quoting Table and Column Names.
9.4.3 Avoiding XSS
XSS or cross-site scripting happens when output isn't escaped properly when
outputting HTML to the browser. For example, if user can enter his name
and instead of
Alexander
he enters
<script>alert('Hello!');</script>,
every
page that outputs user name without escaping it will execute JavaScript
alert('Hello!');
resulting in alert box popping up in a browser. Depending
9.4.
SECURITY BEST PRACTICES
361
on website instead of innocent alert such script could send messages using
your name or even perform bank transactions.
Avoiding XSS is quite easy in Yii. There are generally two cases:
1. You want data to be outputted as plain text.
2. You want data to be outputted as HTML.
If all you need is plain text then escaping is as easy as the following:
<?= \yii\helpers\Html::encode($username) ?>
If it should be HTML we could get some help from HtmlPurier:
<?= \yii\helpers\HtmlPurifier::process($description) ?>
Note that HtmlPurier processing is quite heavy so consider adding caching.
9.4.4 Avoiding CSRF
CSRF is an abbreviation for cross-site request forgery. The idea is that many
applications assume that requests coming from a user browser are made by
the user himself. It could be false.
For example,
an.example.com website has /logout URL that,
using a simple GET, logs user out.
when accessed
As long as it's requested by the user
itself everything is OK but one day bad guys are somehow posting
="http://an.example.com/logout">
<img src
on a forum user visits frequently. Browser
doesn't make any dierence between requesting an image or requesting a
page so when user opens a page with such
img
tag he's being logged out from
an.example.com.
That's the basic idea.
One can say that logging user out is nothing
serious. Well, sending POST isn't much trickier.
In order to avoid CSRF you should always:
1. Follow HTTP specication i.e.
GET should not change application
state.
2. Keep Yii CSRF protection enabled.
9.4.5 Avoiding le exposure
By default server webroot is meant to be pointed to
index.php
web
directory where
is. In case of shared hosting environments it could be impossible
to achieve so we'll end up with all the code, congs and logs in server webroot.
If it's the case don't forget to deny access to everything except
can't be done consider hosting your application elsewhere.
web.
If it
362
CHAPTER 9.
SECURITY
9.4.6 Avoiding debug info and tools at production
In debug mode Yii shows quite verbose errors which are certainly helpful for
development. The thing is that these verbose errors are handy for attacker
as well since these could reveal database structure, conguration values and
parts of your code. Never run production applications with
true
in your
YII_DEBUG
set to
index.php.
You should never enalble Gii at production.
It could be used to get
information about database structure, code and to simply rewrite code with
what's generated by Gii.
Debug toolbar should be avoided at production unless really necessary.
It exposes all the application and cong details possible. If you absolutely
need it check twice that access is properly restricted to your IP only.
Chapter 10
Caching
10.1 Caching
Caching is a cheap and eective 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 dierent 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:
// try retrieving $data from cache
$data = $cache->get($key);
if ($data === false) {
363
364
CHAPTER 10.
CACHING
// $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, les, databases.
Cache components are usually registered as application components so
that they can be globally congurable and accessible.
shows how to congure the
cache
The following code
1
application component to use memcached
with two cache servers:
'components' => [
'cache' => [
'class' => 'yii\caching\MemCache',
'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 dierent one by reconguring
it in the application conguration without modifying the code that uses the
cache.
For example, you can modify the above conguration to use
cache:
'components' => [
'cache' => [
'class' => 'yii\caching\ApcCache',
],
],
1
http://memcached.org/
APC
10.2.
DATA CACHING
Tip:
365
You can register multiple cache application components.
The component named
dependent classes (e.g.
cache
is used by default by many cache-
yii\web\UrlManager).
Supported Cache Storage
Yii supports a wide range of cache storage. The following is a summary:
• yii\caching\ApcCache:
2 extension. This option can
uses PHP APC
be considered as the fastest one when dealing with cache for a centralized thick application (e.g.
one server, no dedicated load balancers,
etc.).
• yii\caching\DbCache:
uses a database table to store cached data. To
use this cache, you must create a table as specied in
\DbCache::$cacheTable.
• yii\caching\DummyCache:
no real caching.
yii\caching
serves as a cache placeholder which does
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 congure 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
->cache->get($key) to attempt retrieving data
worrying that Yii::$app->cache might be null.
• yii\caching\FileCache:
Yii::$app
from the cache without
uses standard les to store cached data.
This is particular suitable to cache large chunk of data, such as page
content.
• yii\caching\MemCache:
3 and memcached4 ex-
uses PHP memcache
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.)
• yii\redis\Cache:
implements a cache component based on Redis
5
key-value store (redis version 2.6.12 or higher is required).
• yii\caching\WinCache: uses PHP WinCache6 (see also7 )
• yii\caching\XCache: uses PHP XCache8 extension.
• Zend Data Cache9 as the underlying caching medium.
2
extension.
http://php.net/manual/en/book.apc.php
http://php.net/manual/en/book.memcache.php
4
http://php.net/manual/en/book.memcached.php
5
http://redis.io/
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
3
366
CHAPTER 10.
CACHING
Tip: You may use dierent 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 le-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
yii\caching\Cache and thus
support the following APIs:
• get():
retrieves a data item from cache with a specied key. A
false
value will be returned if the data item is not found in the cache or is
expired/invalidated.
• set():
• add():
stores a data item identied by a key in cache.
stores a data item identied by a key in cache if the key is not
found in the cache.
• mget():
retrieves multiple data items from cache with the specied
keys.
• mset():
stores multiple data items in cache. Each item is identied by
a key.
• madd():
stores multiple data items in cache. Each item is identied
by a key. If a key already exists in the cache, the data item will be
skipped.
• exists():
returns a value indicating whether the specied key is found
in the cache.
• delete(): removes a data item identied by a key
• flush(): removes all data items from the cache.
from the cache.
false boolean value directly because the
false return value to indicate the data item
cache. You may put false in an array and
Note: Do not cache a
get()
method uses
is not found in the
cache this array instead to avoid this problem.
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
mget()
and
madd()
are provided to
exploit this feature. In case the underlying cache storage does not support
this feature, it will be simulated.
Because
yii\caching\Cache implements ArrayAccess, a cache component
can be used like an array. The followings are some examples:
$cache['var1'] = $value1;
$value2 = $cache['var2'];
// equivalent to: $cache->set('var1', $value1);
// equivalent to: $value2 = $cache->get('var2');
10.2.
DATA CACHING
367
Cache Keys
Each data item stored in cache is uniquely identied 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 dening a cache key is to include all determining
factors in terms of an array. For example,
yii\db\Schema uses the following
key to cache schema information about a database table:
[
];
__CLASS__,
$this->db->dsn,
$this->db->username,
$name,
//
//
//
//
schema class name
DB connection data source name
DB connection login user
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 dierent applications, you should
specify a unique cache key prex for each application to avoid conicts of
cache keys.
This can be done by conguring the
$keyPrefix property.
yii\caching\Cache::
For example, in the application conguration you can
write the following code:
'components' => [
'cache' => [
'class' => 'yii\caching\ApcCache',
'keyPrefix' => 'myapp',
// a unique cache key prefix
],
],
To ensure interoperability, only alphanumeric characters should be used.
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
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
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);
368
CHAPTER 10.
CACHING
$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
\FileDependency
cache dependencies.
For example,
yii\caching
represents the dependency of a le's modication time.
When this dependency changes, it means the corresponding le is modied.
As a result, any outdated le content found in the cache should be invalidated
and the
get()
call should return
false.
Cache dependencies are represented as objects of
descendant classes. When you call
set()
yii\caching\Dependency
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 \yii\caching\FileDependency(['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:
• yii\caching\ChainedDependency:
the dependency is changed if any
of the dependencies on the chain is changed.
• yii\caching\DbDependency:
the dependency is changed if the query
result of the specied SQL statement is changed.
• yii\caching\ExpressionDependency:
the dependency is changed if
the result of the specied PHP expression is changed.
• yii\caching\FileDependency:
the dependency is changed if the le's
last modication time is changed.
• yii\caching\TagDependency:
associates a cached data item with one
or multiple tags. You may invalidate the cached data items with the
specied tag(s) by calling
yii\caching\TagDependency::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.
10.2.
DATA CACHING
369
Query caching requires a
DB connection
and a valid
cache
application
component. The basic usage of query caching is as follows, assuming
a
yii\db\Connection
$db
is
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:
$result = Customer::getDb()->cache(function ($db) {
return Customer::find()->where(['id' => 1])->one();
});
10 ) also support query caching on
Info: Some DBMS (e.g. MySQL
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 exible cache dependencies and are
potentially more ecient.
Congurations
Query caching has three global congurable options through
• enableQueryCache:
yii\db\Connection:
whether to turn on or o query caching.
It de-
faults to true. Note that to eectively turn on query caching, you also
need to have a valid cache, as specied by
• queryCacheDuration:
queryCache.
this represents the number 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.
the default value used when
This property is
yii\db\Connection::cache()
is called
without specifying a duration.
• queryCache:
this represents the ID of the cache application compo-
nent. It defaults to
'cache'.
Query caching is enabled only if there is
a valid cache application component.
Usages
You can use
yii\db\Connection::cache() if you have multiple SQL queries
that need to take advantage of query caching. The usage is as follows,
10
http://dev.mysql.com/doc/refman/5.1/en/query-cache.html
370
CHAPTER 10.
$duration = 60;
$dependency = ...;
CACHING
// cache query results for 60 seconds.
// 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 specied
duration with the specied 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
value of
queryCacheDuration
Sometimes within
cache(),
some particular queries.
$duration
parameter, the
will be used instead.
you may want to disable query caching for
You can use
yii\db\Connection::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
\Command::cache()
yii\db
when building the command. For example,
// 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
yii\db\Command::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();
// ...
10.3.
});
FRAGMENT CACHING
371
return $result;
Limitations
Query caching does not work with query results that contain resource handlers. 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
endCache()
calls. If the content is found in the cache,
beginCache()
beginCache()
and
will
render the cached content and return false, thus skip the content generation
logic.
Otherwise, your content generation logic will be called, and when
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
beginCache()
method.
Be-
yii\widgets
\FragmentCache widget which implements the actual fragment caching funchind the scene, this option array will be used to congure a
tionality.
372
CHAPTER 10.
CACHING
Duration
Perhaps the most commonly used option of fragment caching is
duration.
It species 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 60, which means the
cached content will expire in 60 seconds.
Dependencies
Like data caching, content fragment being cached can also have dependencies. For example, the content of a post being displayed depends on whether
or not the post is modied.
To specify a dependency, set the
an
yii\caching\Dependency
dependency option, which can be either
object or a conguration array for creating a
dependency object. The following code species that the fragment content
depends on the change of the
updated_at
column value:
$dependency = [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(updated_at) FROM post',
];
if ($this->beginCache($id, ['dependency' => $dependency])) {
// ... generate content here ...
}
$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 dierent languages. Therefore, you may want to make the cached content variated according to the
current application language.
To specify cache variations, set the
variations
option, which should be
an array of scalar values, each representing a particular variation factor. For
example, to make the cached content variated by the language, you may use
the following code:
10.3.
FRAGMENT CACHING
373
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
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)) {
// ...content generation logic...
if ($this->beginCache($id2, $options2)) {
// ...content generation logic...
}
$this->endCache();
// ...content generation logic...
}
$this->endCache();
Dierent caching options can be set for the nested caches. For example, the
inner caches and the outer caches can use dierent cache duration values.
Even when the data cached in the outer cache is invalidated, the inner cache
374
CHAPTER 10.
CACHING
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
yii\base\View::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();
}
The
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
yii\filters\PageCache,
It can be used like the following in a controller class:
an action lter.
10.5.
HTTP CACHING
375
public function behaviors()
{
return [
[
'class' => 'yii\filters\PageCache',
'only' => ['index'],
'duration' => 60,
'variations' => [
\Yii::$app->language,
],
'dependency' => [
'class' => 'yii\caching\DbDependency',
'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 dierence is that page caching is implemented as an action lter
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
generating and transmitting the same page content.
To use client-side caching, you may congure
yii\filters\HttpCache
as a lter for controller actions whose rendering result may be cached on the
client side.
HttpCache
only works for
GET
and
HEAD
requests. It can handle
three kinds of cache-related HTTP headers for these requests:
• Last-Modified
• Etag
• Cache-Control
10.5.1
The
Last-Modified
Last-Modified
Header
header uses a timestamp to indicate if the page has been
modied since the client caches it.
376
CHAPTER 10.
You may congure the
CACHING
yii\filters\HttpCache::$lastModified
erty to enable sending the
Last-Modified
header.
prop-
The property should be
a PHP callable returning a UNIX timestamp about the page modication
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' => 'yii\filters\HttpCache',
'only' => ['index'],
'lastModified' => function ($action, $params) {
$q = new \yii\db\Query();
return $q->from('post')->max('updated_at');
},
],
];
}
The above code states that HTTP caching should be enabled for the
action only. It should generate a
Last-Modified
index
HTTP header based on the
last update time of posts. When a browser visits the
index
page for the rst
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 modied 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.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 congure the
to enable sending the
ETag
yii\filters\HttpCache::$etagSeed
property
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
10.5.
HTTP CACHING
377
* @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' => 'yii\filters\HttpCache',
'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
action only. It should generate an
ETag
view
HTTP header based on the title and
content of the requested post. When a browser visits the
view
page for the
rst 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 nd a simple expression that invalidates the cache if
the page content has been modied.
11 , HttpCache will send out both
Note: In compliance to RFC 7232
ETag
and
Last-Modified
headers if they are both congured. And
if the client sends both of the
Modified-Since
10.5.3
The
Cache-Control
Cache-Control
If-None-Match
header and the
If-
header, only the former will be respected.
Header
header species the general caching policy for pages. You
may send it by conguring the
yii\filters\HttpCache::$cacheControlHeader
property with the header value. By default, the following header will be sent:
Cache-Control: public, max-age=3600
11
http://tools.ietf.org/html/rfc7232#section-2.4
378
CHAPTER 10.
CACHING
10.5.4 Session Cache Limiter
When a page uses session, PHP will automatically send some cache-related
HTTP headers as specied 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
congure the
yii\filters\HttpCache::$sessionCacheLimiter
The property can take a string value, including
, and
nocache.
property.
public, private, private_no_expire
12
Please refer to the PHP manual about session_cache_limiter()
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.
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 negotiation (supporting JSON and XML by default);
Customizable object serialization with support for selectable output
elds;
•
•
•
•
•
•
•
Proper formatting of collection data and validation errors;
1
Support for HATEOAS ;
Ecient 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 eort.
Assume you want to expose the user data via RESTful APIs. The user
user DB table, and you have already
app\models\User to access the user data.
data are stored in the
ActiveRecord
class
created the
11.1.1 Creating a Controller
First, create a controller class
app\controllers\UserController
namespace app\controllers;
use yii\rest\ActiveController;
class UserController extends ActiveController
1
http://en.wikipedia.org/wiki/HATEOAS
379
as follows,
380
{
}
CHAPTER 11.
RESTFUL WEB SERVICES
public $modelClass = 'app\models\User';
The controller class extends from
fying
modelClass
as
yii\rest\ActiveController.
app\models\User,
By speci-
the controller knows what model can
be used for fetching and manipulating data.
11.1.2 Conguring URL Rules
Then, modify the conguration about the
urlManager
component in your ap-
plication conguration:
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
],
]
The above conguration 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 Enabling JSON Input
To let the API accept input data in JSON format, congure the
property of the
parsers
request application component to use the yii\web\JsonParser
for JSON input:
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
]
Info: The above conguration is optional.
Without the above
conguration, the API would only recognize
form-urlencoded
and
multipart/form-data
application/x-www-
input formats.
11.1.4 Trying it Out
With the above minimal amount of eort, you have already nished 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
• POST /users: create a new user;
of user listing;
11.1.
•
•
•
•
•
•
QUICK START
381
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.
Info: Yii will automatically pluralize controller names for use in
endpoints. You can congure this using the
::$pluralize-property.
You may access your APIs with the
curl
yii\rest\UrlRule
command like the following,
$ curl -i -H "Accept:application/json" "http://localhost/users"
HTTP/1.1 200 OK
...
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,
see the result is returned in XML format:
$ curl -i -H "Accept:application/xml" "http://localhost/users"
HTTP/1.1 200 OK
...
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,
and you will
382
CHAPTER 11.
RESTFUL WEB SERVICES
<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>
The following command will create a new user by sending a POST request
with the user data in JSON format:
$ curl -i -H "Accept:application/json" -H "Content-Type:application/json" XPOST "http://localhost/users" -d '{"username": "example", "email": "
[email protected]"}'
HTTP/1.1 201 Created
...
Location: http://localhost/users/1
Content-Length: 99
Content-Type: application/json; charset=UTF-8
{"id":1,"username":"example","email":"
[email protected]","created_at
":1414674789,"updated_at":1414674789}
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 specic 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 elds
should be included in the result.
users?fields=id,email
For example, the URL
will only return the
id
and
email
http://localhost/
elds.
http://localhost/
users includes some sensitive elds, such as password_hash, auth_key
Info: You may have noticed that the result of
. You certainly do not want these to appear in your API result.
You can and should lter out these elds as described in the
Response Formatting section.
11.2.
RESOURCES
383
11.1.5 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
\base\Model class.
yii
If you are working with databases (relational or NoSQL),
ActiveRecord to represent resources.
yii\rest\UrlRule to simplify the routing to
it is recommended you use
You may use
your API
endpoints.
While not required, it is recommended that you develop your RESTful
APIs as a separate application, dierent 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
yii\base\Model
or
yii\db\ActiveRecord), for the following reasons:
• yii\base\Model implements the yii\base\Arrayable interface, which
its child classes (e.g.
allows you to customize how you want to expose resource data through
RESTful APIs.
• yii\base\Model
supports input validation, which is useful if your
RESTful APIs need to support data input.
• yii\db\ActiveRecord
provides powerful DB data access and manip-
ulation support, which makes it a perfect t if your resource data is
stored in databases.
In this section, we will mainly describe how a resource class extending from
yii\base\Model (or its child classes) can specify what data may be returned
via RESTful APIs. If the resource class does not extend from yii\base
\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
yii\rest\Serializer.
Second,
the array is serialized into a string in a requested format (e.g. JSON, XML)
by
response formatters.
The rst step is what you should mainly focus
when developing a resource class.
By overriding
data, called
elds,
fields()
and/or
extraFields(),
you may specify what
in the resource can be put into its array representation.
384
CHAPTER 11.
RESTFUL WEB SERVICES
The dierence between these two methods is that the former species the
default set of elds which should be included in the array representation,
while the latter species additional elds which may be included in the array
if an end user requests for them via the
expand query parameter.
For example,
// 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()
yii\base\Model::fields() returns all model attributes as elds,
yii\db\ActiveRecord::fields() only returns the attributes which
By default,
while
have been populated from DB.
fields() to
fields() should
You can override
return value of
add, remove, rename or redene elds. The
be an array.
The array keys are the eld
names, and the array values are the corresponding eld denitions which
can be either property/attribute names or anonymous functions returning
the corresponding eld values. In the special case when a eld name is the
same as its dening 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 ($model) {
return $model->first_name . ' ' . $model->last_name;
},
];
}
// filter out some fields, best used when you want to inherit the parent
implementation
11.2.
RESOURCES
385
// 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 API result, you should examine your data to make
sure they do not contain sensitive information. If there is such
fields()
to lter them out. In
the above example, we choose to lter out
auth_key, password_hash
information, you should override
and
password_reset_token.
Overriding
extraFields()
yii\base\Model::extraFields() returns nothing, while yii
\db\ActiveRecord::extraFields() returns the names of the relations that
By default,
have been populated from DB.
The return data format of
().
Usually,
extraFields()
extraFields()
is the same as that of
fields
is mainly used to specify elds whose values are
objects. For example, given the following eld declaration,
public function fields()
{
return ['id', 'email'];
}
public function extraFields()
{
return ['profile'];
}
the request with
http://localhost/users?fields=id,email&expand=profile
return the following JSON data:
[
]
{
},
...
"id": 100,
"email": "
[email protected]",
"profile": {
"id": 100,
"age": 30,
}
may
386
CHAPTER 11.
RESTFUL WEB SERVICES
11.2.2 Links
2
HATEOAS , 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.
yii
getLinks()
Your resource classes may support HATEOAS by implementing the
\web\Linkable interface.
The interface contains a single method
which should return a list of
self
use
use
use
use
links.
Typically, you should return at least the
link representing the URL to the resource object itself. For example,
yii\db\ActiveRecord;
yii\web\Link;
yii\web\Linkable;
yii\helpers\Url;
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": {
"href": "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
387
for RESTful APIs returning collections. For example, the following action
returns a data provider about the post resources:
namespace app\controllers;
use yii\rest\Controller;
use yii\data\ActiveDataProvider;
use app\models\Post;
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,
yii\rest
\Serializer will take out the current page of resources and serialize them as
an array of resource objects. Additionally, yii\rest\Serializer 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:
yii\rest\Controller and yii\rest\ActiveController.
The dierence between these two controllers is that the latter provides a
default set of actions that are specically 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
yii\rest\ActiveController,
which will allow
you to create powerful RESTful APIs with minimal code.
Both
yii\rest\Controller
and
yii\rest\ActiveController
provide
the following features, some of which will be described in detail in the next
few sections:
388
CHAPTER 11.
RESTFUL WEB SERVICES
• HTTP method validation;
• Content negotiation and Data formatting;
• Authentication;
• Rate limiting.
yii\rest\ActiveController 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 controller
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 application. The only dierence is that instead of rendering the result using a view
by calling the
data. The
render()
method, for RESTful actions you directly return the
serializer and the response object 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
yii\rest\Controller
are imple-
mented in terms of lters. In particular, the following lters will be executed
in the order they are listed:
• contentNegotiator:
supports content negotiation, to be explained in
the Response Formatting section;
• verbFilter: supports HTTP method validation;
• yii\filters\AuthMethod: supports user authentication,
to be ex-
plained in the Authentication section;
• rateLimiter:
supports rate limiting, to be explained in the Rate Lim-
iting section.
These named lters are declared in the
behaviors()
method.
You may
override this method to congure individual lters, disable some of them,
or add your own lters. For example, if you only want to use HTTP basic
authentication, you may write the following code:
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
11.3.
}
CONTROLLERS
389
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
11.3.3 Extending ActiveController
If your controller class extends from
set its
|modelClass
yii\rest\ActiveController, you should
property to be the name of the resource class that you
plan to serve through this controller. The class must extend from
\ActiveRecord.
yii\db
Customizing Actions
By default,
yii\rest\ActiveController
provides the following actions:
index: list resources page by page;
view: return the details of a specied resource;
create: create a new resource;
update: update an existing resource;
delete: delete the specied resource;
options: return the supported HTTP methods.
these actions are declared through the actions()
•
•
•
•
•
•
All
method.
congure these actions or disable some of them by overriding the
You may
actions()
method, like shown the following,
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
conguration options are available.
390
CHAPTER 11.
RESTFUL WEB SERVICES
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
yii\rest\ActiveController, this
checkAccess() method like the following,
resource(s). With
riding the
can be done by over-
/**
* 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 \yii\base\Model $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
}
The
checkAccess()
method will be called by the default actions of
\ActiveController.
yii\rest
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
the
user/create
POST /users
would mean accessing
action. This can be done easily by conguring the
urlManager
application component in the application conguration like the following:
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
11.4.
ROUTING
],
]
391
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
Compared to the URL management for Web applications, the main new
thing above is the use of
quests.
yii\rest\UrlRule
for routing RESTful API re-
This special URL rule class will create a whole set of child URL
rules to support routing and URL creation for the specied 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;
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.
may congure the only and except options to explicitly list which actions
•
•
•
•
•
•
•
•
•
You
to support or which actions should be disabled, respectively. For example,
[
],
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'except' => ['delete', 'create', 'update'],
You may also congure
patterns
or
extraPatterns
to redene existing patterns
or add new patterns supported by this rule. For example, to support a new
action
search
by the endpoint
GET /users/search,
option as follows,
[
]
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'extraPatterns' => [
'GET search' => 'search',
],
congure the
extraPatterns
392
CHAPTER 11.
RESTFUL WEB SERVICES
You may have noticed that the controller ID
as
users
in the endpoint URLs.
user
This is because
appears in plural form
yii\rest\UrlRule
auto-
matically pluralizes controller IDs when creating child URL rules. You may
disable this behavior by setting
yii\rest\UrlRule::$pluralize to be false.
Info: The pluralization of controller IDs is done by
\Inflector::pluralize().
The method respects special plu-
ralization rules. For example, the word
boxes
instead of
yii\helpers
box
will be pluralized as
boxs.
In case when the automatic pluralization does not meet your requirement,
you may also congure the
yii\rest\UrlRule::$controller
property to
explicitly specify how to map a name used in endpoint URLs to a controller
ID. For example, the following code maps the name
u
to the controller ID
user.
[
'class' => 'yii\rest\UrlRule',
'controller' => ['u' => 'user'],
]
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 aect the response format, such
as media type, language, version, etc. This process is also known as
3
content negotiation .
2. Convert resource objects into arrays, as described in the Resources
section. This is done by
yii\rest\Serializer.
3. Convert arrays into a string in the format as determined by the content
negotiation step.
with the
response
This is done by
response formatters
registered
application component.
11.5.1 Content Negotiation
Yii supports content negotiation via the
lter.
yii\filters\ContentNegotiator
yii\rest\Controller is
The RESTful API base controller class
equipped with this lter under the name of
contentNegotiator.
The lter
provides response format negotiation as well as language negotiation.
example, if a RESTful API request contains the following header,
3
http://en.wikipedia.org/wiki/Content_negotiation
For
11.5.
RESPONSE FORMATTING
393
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
yii\filters\ContentNegotiator lter will check the Accept HTTP header
in the request and set the response format to be 'json'. After the action
is executed and returns the resulting resource object or collection, yii\rest
\Serializer will convert the result into an array. And nally, yii\web
\JsonResponseFormatter 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 congure the
contentNegotiator
formats
property of the
lter like the following in your API controller classes:
use yii\web\Response;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats']['text/html'] = Response::
FORMAT_HTML;
return $behaviors;
}
394
CHAPTER 11.
The keys of the
formats
RESTFUL WEB SERVICES
property are the supported MIME types, while the
values are the corresponding response format names which must be supported in
yii\web\Response::$formatters.
11.5.2 Data Serializing
As we have described above,
yii\rest\Serializer
is the central piece re-
sponsible for converting resource objects or collections into arrays. It recognizes objects implementing yii\base\ArrayableInterface as well as yii
\data\DataProviderInterface. The former is mainly implemented by resource objects, while the latter resource collections.
You may congure the serializer by setting the
$serializer
yii\rest\Controller::
property with a conguration array. For example, sometimes
you may want to help simplify the client development work by including
pagination information directly in the response body.
the
yii\rest\Serializer::$collectionEnvelope
To do so, congure
property as follows:
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
public $serializer = [
'class' => 'yii\rest\Serializer',
'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
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,
11.6.
AUTHENTICATION
},
...
395
...
],
"_links": {
"self": {
"href": "http://localhost/users?page=1"
},
"next": {
"href": "http://localhost/users?page=2"
},
"last": {
"href": "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
API requests should always be sent via HTTPS to prevent
man-in-the-middle (MitM) attacks.
user,
There are dierent ways to send an access token:
•
4
HTTP Basic Auth : the access token is sent as the username.
This
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.
•
4
5
5
OAuth 2 : the access token is obtained by the consumer from an autho-
http://en.wikipedia.org/wiki/Basic_access_authentication
http://oauth.net/2/
396
CHAPTER 11.
RESTFUL WEB SERVICES
6
rization server and sent to the API server via HTTP Bearer Tokens ,
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. Congure the
•
•
Set the
Set the
user
application component:
enableSession property to be false.
loginUrl property to be null to show
a HTTP 403 error
instead of redirecting to the login page.
2. Specify which authentication methods you plan to use by conguring
the
authenticator
behavior in your REST controller classes.
yii\web\IdentityInterface::findIdentityByAccessToken()
user identity class.
3. Implement
in your
Step 1 is not required but is recommended for RESTful APIs which should
be stateless. When
enableSession
is false, the user authentication 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 congure
enableSession
of the
user
application
component in application congurations if you are developing
RESTful APIs in terms of an application. If you develop RESTful
APIs as a module, you may put the following line in the module's
init()
method, like the following:
`php
public function init() {
parent::init();
\Yii::$app->user->enableSession = false;
}
`
For example, to use HTTP Basic Auth, you may congure the
behavior as follows,
use yii\filters\auth\HttpBasicAuth;
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
6
http://tools.ietf.org/html/rfc6750
authenticator
11.6.
AUTHENTICATION
397
If you want to support all three authentication methods explained above,
you can use
use
use
use
use
CompositeAuth
like the following,
yii\filters\auth\CompositeAuth;
yii\filters\auth\HttpBasicAuth;
yii\filters\auth\HttpBearerAuth;
yii\filters\auth\QueryParamAuth;
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
conguration array.
Implementation of
findIdentityByAccessToken() is application specic.
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 yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]);
}
}
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
for HTTP Basic Auth).
WWW-Authenticate
header
398
CHAPTER 11.
RESTFUL WEB SERVICES
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.
yii\rest\ActiveController, you may
override the yii\rest\Controller::checkAccess() method to perform auIf your controllers extend from
thorization check. The method will be called by the built-in actions provided
by
yii\rest\ActiveController.
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.
user identity class should implement yii
\filters\RateLimitInterface. This interface requires implementation of
To enable rate limiting, the
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 allowance
and timestamp information. With those dened, then
saveAllowance()
loadAllowance()
and
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-
yii\filters\RateLimiter congured as an action lter for
yii\rest\Controller to perform rate limiting check. The rate limiter will
throw a yii\web\TooManyRequestsHttpException when the rate limit is
matically use
exceeded.
You may congure the rate limiter as follows in your REST controller
classes:
public function behaviors()
{
11.8.
VERSIONING
399
$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
yii\filters\RateLimiter::
$enableRateLimitHeaders to be false, as shown in the above code example.
You may disable these headers by conguring
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.
7 for more information on de-
Tip: Refer to Semantic Versioning
signing API version numbers.
One common way to implement API versioning is to embed the version
number in the API URLs. For example,
for the
/users
http://example.com/v1/users
stands
endpoint of API version 1.
Another method of API versioning, which has gained momentum recently, 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
7
http://semver.org/
400
CHAPTER 11.
RESTFUL WEB SERVICES
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 module), 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 resource and controller classes serving that specic version. To better separate
code responsibility, you may keep a common set of base resource and controller classes, and subclass them in each individual version module. Within
the subclasses, implement the concrete code such as
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
models/
User.php
Post.php
Module.php
v2/
controllers/
UserController.php
PostController.php
models/
User.php
Post.php
Module.php
Your application conguration would look like:
return [
'modules' => [
'v1' => [
'class' => 'app\modules\v1\Module',
],
'v2' => [
'class' => 'app\modules\v2\Module',
Model::fields().
11.9.
];
ERROR HANDLING
401
],
],
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user',
'v1/post']],
['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user',
'v2/post']],
],
],
],
http://example.com/v1/users will return the list
http://example.com/v2/users will return version 2
As a result of the above code,
of users in version 1, while
users.
Thanks to modules, the code for dierent 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-
contentNegotiator behavior. The
yii\web\Response::$acceptParams
tent negotiation feature provided by the
contentNegotiator
behavior will set the
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, yii\web\Response::$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
provide the appropriate functionality.
Since minor versions by denition require maintaining backward compatibility, 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.,
yii\web\NotFoundHttpException
represents a 404 status code).
Yii will send the response along with the corresponding HTTP status code
402
CHAPTER 11.
RESTFUL WEB SERVICES
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:
• 201:
The
OK. Everything worked as expected.
A resource was successfully created in response to a
Location
POST
request.
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
• 304:
• 400:
DELETE
request).
The resource was not modied. You can use the cached version.
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:
• 403:
Authentication failed.
The authenticated user is not allowed to access the specied API
endpoint.
• 404:
• 405:
The requested resource does not exist.
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:
• 500:
Too many requests. The request was rejected due to rate limiting.
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 dierent HTTP statuses to indicate
dierent errors, you would like to always use 200 as HTTP status and enclose
11.9.
ERROR HANDLING
403
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 conguration:
return [
// ...
'components' => [
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => function ($event) {
$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
rameter.
suppress_response_code
is passed as a
GET
pa-
404
CHAPTER 11.
RESTFUL WEB SERVICES
Chapter 12
Development Tools
405
406
CHAPTER 12.
DEVELOPMENT TOOLS
Error: not existing le: https://github.com/yiisoft/yii2-debug/blob/master/docs
407
Error: not existing le: https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/REA
408
CHAPTER 12.
DEVELOPMENT TOOLS
Error: not existing le: https://github.com/yiisoft/yii2-apidoc
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 the implementation works according to our initial plan.
In the case of a 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 does it for us.
The code that veries the 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 these 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 the 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.
After it's done the process is repeated again for another feature or improvement. If the existing feature is to be changed, tests should be changed as
well.
409
410
CHAPTER 13.
Tip:
TESTING
If you feel that you are losing 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
us to focus on what 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 or less coupled components.
So to sum up the pros of such an approach are the following:
•
Keeps you focused on one thing at a time which results in improved
planning and implementation.
•
Results in test-covering more features in greater detail i.e. if tests are
OK most likely nothing's broken.
In the long term it usually gives you a good time-saving eect.
Tip:
If you want to know more about the principles for gathering
software requirements and modeling the subject matter it's good
1
to learn Domain Driven Development (DDD) .
13.1.2 When and how to test
While the test rst 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 constantly.
•
•
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 implementation.
•
•
Project is a legacy one to be gradually renewed.
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 anymore complex.
It's a one-time project that will no longer be worked on.
Still if you have time it's good to automate testing in these cases as well.
13.1.3 Further reading
•
1
Test Driven Development: By Example / Kent Beck. ISBN: 0321146530.
https://en.wikipedia.org/wiki/Domain-driven_design
13.2.
TESTING ENVIRONMENT SETUP
411
13.2 Testing environment setup
Note: This section is under development.
Yii2 has ocially maintained integration with
Codeception2
testing framework
that allows you to create the following test types:
•
•
Unit testing - veries that a single unit of code is working as expected;
Functional testing - veries scenarios from a user's perspective via
browser emulation;
•
Acceptance testing - veries scenarios from a user's perspective in a
browser.
Yii provides ready to use test sets for all three test types in both
and
yii2-advanced4
project templates.
yii2-basic3
5
In order to run tests you need to install Codeception . 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
status.
composer global
It should output:
Changed current directory to <directory>
Then add
we're able
<directory>/vendor/bin to you PATH environment
to use codecept from command line globally.
variable.
Now
13.3 Unit Tests
Note: This section is under development.
A unit test veries that a single unit of code is working as expected.
object-oriented programming, the most basic code unit is a class.
In
A unit
test thus mainly needs to verify that each of the class interface methods
works properly. That is, given dierent input parameters, the test veries
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, Codeception so it's recommended to go through their docs:
•
•
2
6
PHPUnit docs starting from chapter 2 .
7
Codeception Unit Tests .
https://github.com/Codeception/Codeception
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
6
http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html
7
http://codeception.com/docs/06-UnitTests
3
412
CHAPTER 13.
TESTING
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.3.2 Framework unit tests
If you want to run unit tests for Yii framework itself follow Getting started
8
with Yii2 development .
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.
13.6 Fixtures
Fixtures are an important part of testing. Their main purpose is to set up
the environment in a xed/known state so that your tests are repeatable and
run in an expected way. Yii provides a xture framework that allows you to
dene your xtures precisely and use them easily.
A key concept in the Yii xture framework is the so-called
xture object.
A xture object represents a particular aspect of a test environment and is
an instance of
use
UserFixture
yii\test\Fixture
or its child class. For example, you may
to make sure the user DB table contains a xed set of data.
8
https://github.com/yiisoft/yii2/blob/master/docs/internals/
getting-started.md
13.6.
FIXTURES
413
You load one or multiple xture objects before running a test and unload
them when nishing.
A xture may depend on other xtures, specied via its
::$depends property.
yii\test\Fixture
When a xture is being loaded, the xtures it depends
on will be automatically loaded BEFORE the xture; and when the xture is
being unloaded, the dependent xtures will be unloaded AFTER the xture.
13.6.1 Dening a Fixture
To dene a xture, create a new class by extending
yii\test\ActiveFixture.
yii\test\Fixture
or
The former is best suited for general purpose
xtures, while the latter has enhanced features specically designed to work
with database and ActiveRecord.
The following code denes a xture about the
User
ActiveRecord and the
corresponding user table.
<?php
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserFixture extends ActiveFixture
{
public $modelClass = 'app\models\User';
}
Tip: Each
ActiveFixture
is about preparing a DB table for test-
yii
\test\ActiveFixture::$tableName property or the yii\test
\ActiveFixture::$modelClass property. If the latter, the ta-
ing purpose. You may specify the table by setting either the
ble name will be taken from the
ActiveRecord
class specied by
modelClass.
Note:
yii\test\ActiveFixture is only suited for SQL databases.
For NoSQL databases, Yii provides the following
ActiveFixture
classes:
•
•
yii\mongodb\ActiveFixture
Elasticsearch: yii\elasticsearch\ActiveFixture
Mongo DB:
(since
version 2.0.2)
ActiveFixture xture is usually provided in a
located at FixturePath/data/TableName.php, where FixturePath stands for
directory containing the xture class le, and TableName is the name of
The xture data for an
le
the
the
table associated with the xture. In the example above, the le should be
@app/tests/fixtures/data/user.php.
The data le should return an array of
data rows to be inserted into the user table. For example,
414
CHAPTER 13.
TESTING
<?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
and
user2,
user1
respectively.
Also, you do not need to specify the data for auto-incremental columns.
Yii will automatically ll the actual values into the rows when the xture is
being loaded.
Tip: You may customize the location of the data le by setting
yii\test\ActiveFixture::$dataFile property. You may
also override yii\test\ActiveFixture::getData() to provide
the
the data.
As we described earlier, a xture may depend on other xtures. For example,
a
UserProfileFixture
may need to depends on
UserFixture
because the user
prole table contains a foreign key pointing to the user table. The dependency is specied via the
yii\test\Fixture::$depends
property, like the
following,
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserProfileFixture extends ActiveFixture
{
public $modelClass = 'app\models\UserProfile';
public $depends = ['app\tests\fixtures\UserFixture'];
}
The dependency also ensures, that the xtures are loaded and unloaded in
UserFixture will always be loaded
UserProfileFixture to ensure all foreign key references exist and will
unloaded after UserProfileFixture has been unloaded for the same reason.
a well dened order. In the above example
before
be
13.6.
FIXTURES
415
In the above, we have shown how to dene a xture about a DB table.
To dene a xture not related with DB (e.g.
a xture about certain les
and directories), you may extend from the more general base class
\Fixture
and override the
load()
and
unload()
yii\test
methods.
13.6.2 Using Fixtures
If you are using CodeCeption
the
yii2-codeception
9 to test your code, you should consider using
extension which has built-in support for loading and
accessing xtures. If you are using other testing frameworks, you may use
yii\test\FixtureTrait
in your test cases to achieve the same goal.
In the following we will describe how to write a
using
UserProfile
unit test class
yii2-codeception.
In your unit test class extending
yii\codeception\DbTestCase
\codeception\TestCase, declare which
fixtures() method. For example,
or
yii
xtures you want to use in the
namespace app\tests\unit\models;
use yii\codeception\DbTestCase;
use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase
{
public function fixtures()
{
return [
'profiles' => UserProfileFixture::className(),
];
}
// ...test methods...
}
The xtures listed in the
fixtures()
method will be automatically loaded
before running every test method in the test case and unloaded after nishing every test method.
And as we described before, when a xture is
being loaded, all its dependent xtures will be automatically loaded rst.
In the above example, because
UserProfileFixture
depends on
UserFixture,
when running any test method in the test class, two xtures will be loaded
sequentially:
UserFixture
and
UserProfileFixture.
When specifying xtures in
fixtures(),
you may use either a class name
or a conguration array to refer to a xture. The conguration array will let
you customize the xture properties when the xture is loaded.
You may also assign an alias to a xture.
UserProfileFixture
9
is aliased as
http://codeception.com/
profiles.
In the above example, the
In the test methods, you may then
416
CHAPTER 13.
access a xture object using its alias. For example,
the
UserProfileFixture
Because
TESTING
$this->profiles will return
object.
UserProfileFixture
extends from
ActiveFixture,
you may further
use the following syntax to access the data provided by the xture:
// 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 Dening and Using Global Fixtures
The xtures described above are mainly used by individual test cases.
In
most cases, you also need some global xtures that are applied to ALL or
many test cases. An example is
yii\test\InitDbFixture
which does two
things:
•
Perform some common initialization tasks by executing a script located
•
Disable the database integrity check before loading other DB xtures,
at
@app/tests/fixtures/initdb.php;
and re-enable it after other DB xtures are unloaded.
Using global xtures is similar to using non-global ones.
ence is that you declare these xtures in
globalFixtures()
instead of
fixtures().
The only dier-
yii\codeception\TestCase::
When a test case loads xtures, it
will rst load global xtures and then non-global ones.
yii\codeception\DbTestCase already declares InitDbFixture
globalFixtures() method. This means you only need to work with @app
/tests/fixtures/initdb.php if you want to do some initialization work before
By default,
in its
each test.
You may otherwise simply focus on developing each individual
test case and the corresponding xtures.
13.6.4 Organizing Fixture Classes and Data Files
By default, xture classes look for the corresponding data les under the
data
les.
folder which is a sub-folder of the folder containing the xture class
You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch dierent data
les for the same xture class for dierent tests. We thus recommend that
you organize the data les in a hierarchical way that is similar to your class
namespaces. For example,
13.6.
FIXTURES
417
# under folder tests\unit\fixtures
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 xture data les between tests and use
them as you need.
Note: In the example above xture les are named only for example purpose. In real life you should name them according to which
xture class your xture classes are extending from. For example, if you are extending from
yii\test\ActiveFixture
for DB
xtures, you should use DB table names as the xture data le
names; If you are extending from
yii\mongodb\ActiveFixture
for MongoDB xtures, you should use collection names as the le
names.
The similar hierarchy can be used to organize xture class les. Instead of
using
data
as the root directory, you may want to use
fixtures
as the root
directory to avoid conict with the data les.
13.6.5 Summary
Note: This section is under development.
In the above, we have described how to dene and use xtures. Below we
summarize the typical workow 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 xtures: clean up the relevant DB tables and populate them
with xture data;
•
•
Perform the actual test;
Unload xtures.
3. Repeat Step 2 until all tests nish.
To be cleaned up below
418
CHAPTER 13.
TESTING
13.7 Managing Fixtures
Note: This section is under development.
todo: this tutorial may be merged with the above part of testxtures.md
Fixtures are important part of testing. Their main purpose is to populate
you with data that needed by testing dierent cases. With this data using
your tests becoming more ecient and useful.
Yii supports xtures via the
yii fixture
command line tool. This tool
supports:
•
•
•
Loading xtures to dierent storage such as: RDBMS, NoSQL, etc;
Unloading xtures in dierent ways (usually it is clearing storage);
Auto-generating xtures and populating it with random data.
13.7.1 Fixtures format
Fixtures are objects with dierent methods and congurations, refer to o-
10 on them. Lets assume we have xtures data to load:
cial documentation
#users.php file under fixtures data path, by default @tests\unit\fixtures\
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 xture that loads data into database then these rows will be
applied to
users
table.
If we are using nosql xtures, for example
xture, then this data will be applied to
users
mongodb
mongodb collection. In order
to learn about implementing various loading strategies and more, refer to
10
https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md
13.7.
MANAGING FIXTURES
419
11 . Above xture example was auto-generated by yii2
ocial documentation
-faker
extension, read more about it in these section. Fixture classes name
should not be plural.
13.7.2 Loading xtures
Fixture classes should be suxed by
searched under
tests\unit\fixtures
Fixture
class. By default xtures will be
namespace, you can change this behavior
with cong or command options. You can exclude some xtures due load or
unload by specifying
-
before its name like
-User.
To load xture, run the following command:
yii fixture/load <fixture_name>
The required
fixture_name
parameter species a xture name which data will
be loaded. You can load several xtures 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 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: tests\unit\fixtures.
yii fixture User --namespace='alias\my\custom\namespace'
// load global fixture `some\name\space\CustomFixture` 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='some\name\space\Custom'
13.7.3 Unloading xtures
To unload xture, run the following command:
11
https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md
420
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 Congure Command Globally
While command line options allow us to congure the migration command
on-the-y, sometimes we may want to congure the command once for all.
For example you can congure dierent migration path as follows:
'controllerMap' => [
'fixture' => [
'class' => 'yii\console\controllers\FixtureController',
'namespace' => 'myalias\some\custom\namespace',
'globalFixtures' => [
'some\name\space\Foo',
'other\name\space\Bar'
],
],
]
13.7.5 Auto-generating xtures
Yii also can auto-generate xtures for you based on some template.
You
can generate your xtures with dierent data on dierent languages and
formats. These feature is done by Faker
13 for more docs.
See extension guide
12
13
12 library and yii2-faker extension.
https://github.com/fzaninotto/Faker
https://github.com/yiisoft/yii2/tree/master/extensions/faker
Chapter 14
Special Topics
421
422
CHAPTER 14.
SPECIAL TOPICS
Error: not existing le: https://github.com/yiisoft/yii2-appadvanced/blob/master/docs/guide/README.md
14.1.
CREATING YOUR OWN APPLICATION STRUCTURE
423
14.1 Creating your own Application structure
Note: This section is under development.
1 and advanced2 project templates are great for most of your
While the basic
needs, you may want to create your own project template with which to start
your projects.
Project templates in Yii are simply repositories containing a
composer.json
le, and registered as a Composer package. Any repository can be identied
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.1.1 Clone the Basic Template
The rst 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.1.2 Modify the Files
composer.json to reect your template. Change
name, description, keywords, homepage, license, and support values to describe your new template. Also adjust the require, require-dev, suggest, and
Next, you'll want to modify the
the
other options to match your template's requirements.
Note: In the
extra
composer.json
le, use the
writable
parameter under
to specify per le permissions to be set after an application
is created using the template.
Next, actually modify the structure and contents of the application as you
would like the default to be. Finally, update the README le to be applicable to your template.
14.1.3 Make a Package
With the template dened, create a Git repository from it, and push your
3 is the best
les there. If you're going to open source your template, Github
1
https://github.com/yiisoft/yii2-app-basic
https://github.com/yiisoft/yii2-app-advanced
3
http://github.com
2
424
CHAPTER 14.
SPECIAL TOPICS
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
4
templates, the package should be registered at Packagist . For private templates, it is a bit more tricky to register the package. For instructions, see
5
the Composer documentation .
14.1.4 Use the Template
That's all that's required to create a new Yii project template. Now you can
create projects using your template:
composer global require "fxp/composer-asset-plugin:1.0.0"
composer create-project --prefer-dist --stability=dev mysoft/yii2-appcoolone new-project
14.2 Console applications
Besides the rich features for building web applications, Yii also has full featured support for console applications which are mainly used to create background and maintainance tasks that need to be performed for a website.
The structure of console applications is very similar to a Yii web application. It consists of one or more
yii\console\Controller 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.
Both project templates already have a console application with them.
You can run it by calling the
yii
script, which is located in the base directory
of the repository. This will give you a list of available commands when you
run it without any further parameters:
4
5
https://packagist.org/
https://getcomposer.org/doc/05-repositories.md#hosting-your-own
14.2.
CONSOLE APPLICATIONS
425
As you can see in the screenshot, Yii has already dened a set of commands that are available by default:
• AssetController - Allows you to combine and compress your JavaScript
and CSS les. You can learn more about this command in the Assets
Section.
• CacheController - Allows you
• FixtureController - Manages
testing purposes.
to ush application caches.
xture data loading and unloading for
This command is described in more detail in the
Testing Section about Fixtures.
• HelpController - Provides help information about console commands,
this is the default command and prints what you have seen in the above
output.
• MessageController
les.
- Extracts messages to be translated from source
To learn more about this command, please refer to the I18N
Section.
• MigrateController
- Manages application migrations. Database mi-
grations are described in more detail in the Database Migration Section.
426
CHAPTER 14.
SPECIAL TOPICS
14.2.1 Usage
You execute a console controller action using the following syntax:
yii <route> [--option1=value1 --option2=value2 ... argument1 argument2 ...]
<route>
In the above,
refers to the route to the controller action. The options
will populate the class properties and arguments are the parameters of the
action method.
For example, the
::$migrationTable
MigrateController::actionUp() with MigrateController
set to
migrations
and a limit of 5 migrations can be
called like so:
yii migrate/up 5 --migrationTable=migrations
Note:
When using
*
in console, don't forget to quote it as
"*"
in
order to avoid executing it as a shell glob that will be replaced
by all le names of the current directory.
14.2.2 The entry script
The console application entry script is equivalent to the
index.php
bootstrap
le used for the web application. The console entry script is typically called
yii,
and located in your application's root directory. It 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 yii\console\Application($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 to
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
14.2.
CONSOLE APPLICATIONS
427
application entry script has debugging enabled by default to provide a more
developer-friendly environment.
14.2.3 Conguration
As can be seen in the code above, the console application uses its own conguration le, named
console.php.
In this le you should congure various
application components and properties for the console application in particular.
If your web application and console application share a lot of conguration parameters and values, you may consider moving the common parts
into a separate le, and including this le in both of the application congurations (web and console). You can see an example of this in the advanced
project template.
Tip: Sometimes, you may want to run a console command using
an application conguration that is dierent from the one specied in the entry script. For example, you may want to use the
yii migrate
command to upgrade your test databases, which are
congured in each individual test suite. To change the conguration dynamically, simply specify a custom application conguration le via the
appconfig
option when executing the command:
yii <route> --appconfig=path/to/config.php ...
14.2.4 Creating your own console commands
Console Controller and Action
A console command is dened as a controller class extending from
\console\Controller.
yii
In the controller class, you dene 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 subcommand.
When running a command, you need to specify the route to the controller
action. For example, the route
corresponds to the
migrate/create
invokes the sub-command that
MigrateController::actionCreate() action method.
If
a route oered during execution does not contain an action ID, the default
action will be executed (as with a web controller).
Options
By overriding the
yii\console\Controller::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
428
CHAPTER 14.
syntax
--OptionName=OptionValue.
This will assign
SPECIAL TOPICS
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 rst argument corresponds to the rst pa-
rameter, the second corresponds to the second, and so on.
If not enough
arguments are provided when the command is called, the corresponding parameters will take the declared default values, if dened. 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 following example shows how to declare arguments:
class ExampleController extends \yii\console\Controller
{
// 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. Conventionally, a command returns
0
to indicate that everything is OK. If the
command returns a number greater than zero, that's considered to be indicative of an error. The number returned will be the error code, potentially
usable to nd out details about the error. For example
1
could stand gener-
ally for an unknown error and all codes above would be reserved for specic
cases: input errors, missing les, and so forth.
14.3.
CORE VALIDATORS
429
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 predened constants you can use:
• Controller::EXIT_CODE_NORMAL with value of 0;
• Controller::EXIT_CODE_ERROR with value of 1.
It's a good practice to dene meaningful constants for your controller in case
you have more error code types.
Formatting and colors
Yii console supports formatted output that is automatically degraded to
non-formatted one if it's not supported by terminal running the command.
Outputting formatted strings is simple. Here's how to output some bold
text:
$this->stdout("Hello?\n", Console::BOLD);
If you need to build string dynamically combining multiple styles it's better
to use
ansiFormat:
$name = $this->ansiFormat('Alex', Console::FG_YELLOW);
echo "Hello, my name is $name.";
14.3 Core Validators
Yii provides a set of commonly used core validators, found primarily under the
yii\validators
names, you may use
namespace. Instead of using lengthy validator class
aliases
to specify the use of these core validators. For
example, you can use the alias
\RequiredValidator
required
to refer to the
yii\validators
class:
public function rules()
{
return [
[['email', 'password'], 'required'],
];
}
The
yii\validators\Validator::$builtInValidators
all supported validator aliases.
property declares
430
CHAPTER 14.
SPECIAL TOPICS
In the following, we will describe the main usage and properties of every
core validator.
14.3.1 boolean
[
// 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
trueValue
Note:
falseValue.
and
Defaults to
that of
false.
Because data input submitted via HTML forms are all
strings, you normally should leave the
strict
property as false.
14.3.2 captcha
[
['verificationCode', 'captcha'],
]
yii\captcha\CaptchaAction
yii\captcha\Captcha to make sure an input is the same as the verication code displayed by CAPTCHA widget.
• caseSensitive: whether the comparison of the verication code is case
This validator is usually used together with
and
sensitive. Defaults to false.
• captchaAction:
the route corresponding to the
renders the CAPTCHA image. Defaults to
• skipOnEmpty:
CAPTCHA action
that
'site/captcha'.
whether the validation can be skipped if the input is
empty. Defaults to false, which means the input is required.
14.3.3 compare
[
]
// 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' => '>='],
14.3.
CORE VALIDATORS
431
This validator compares the specied input value with another one and make
sure if their relationship is as specied by the
• compareAttribute:
operator
property.
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
_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 specied,
suxed with
this property will take precedence.
• operator:
==, meaning checking
compareAttribute or compareValue.
the comparison operator. Defaults to
if the input value is equal to that of
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.
!=:
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.3.4 date
[
]
[['from_date', 'to_date'], '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 specied via
• format:
timestampAttribute.
the date/time format that the value being validated should be
6
in. This can be a date time pattern as described in the ICU manual .
Alternatively this can be a string prexed with
php:
6
http://userguide.icu-project.org/formatparse/datetime#
TOC-Date-Time-Format-Syntax
representing a
432
CHAPTER 14.
format that can be recognized by the PHP
to
SPECIAL TOPICS
Datetime
class. Please refer
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.
This can be the same attribute as the one being validated. If this is the
case, the original value will be overwritten with the timestamp value
after validation. See Handling date input with the DatePicker for a
usage example.
In case the input is optional you may also want to add a default value lter
in addition to the date validator to ensure empty input is stored as
Other wise you may end up with dates like
1970-01-01
[
],
0000-00-00
NULL.
in your database or
in the input eld of a date picker.
[['from_date', 'to_date'], 'default', 'value' => null],
[['from_date', 'to_date'], 'date'],
14.3.5 default
[
// 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'],
]
// 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.3.
CORE VALIDATORS
433
14.3.6 double
[
]
// 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.3.7 email
[
]
// 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.
<
[email protected]>).
• checkDNS, whether to check
John Smith
Defaults to false.
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.3.8 exist
[
// 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']],
434
CHAPTER 14.
SPECIAL TOPICS
// 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
to be validated. If the key and the value are the same, you can just
specify the value.
• filter:
additional lter 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
where()
yii\db\Query::
on the format of query condition), or an anonymous function
with the signature
function ($query),
where
$query
is the
Query
object
that you can modify in the function.
• allowArray:
to false.
whether to allow the input value to be an array. Defaults
If this property is true and the input is an array, then ev-
ery element of the array must exist in the target column. Note that
this property cannot be set true if you are validating against multiple
columns by setting
targetAttribute
as an array.
14.3.9 file
[
]
// 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],
This validator checks if the input is a valid uploaded le.
14.3.
CORE VALIDATORS
• extensions:
435
a list of le name extensions that are allowed to be up-
loaded. This can be either an array or a string consisting of le extension names separated by space or comma (e.g. gif, jpg). Extension
names are case-insensitive.
Defaults to null, meaning all le name
extensions are allowed.
• mimeTypes:
a list of le MIME types that are allowed to be uploaded.
This can be either an array or a string consisting of le 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 le.
Defaults to null, meaning no lower limit.
• maxSize:
the maximum number of bytes allowed for the uploaded le.
Defaults to null, meaning no upper limit.
• maxFiles:
the maximum number of les that the given attribute can
hold. Defaults to 1, meaning the input must be a single uploaded le.
If it is greater than 1, then the input must be an array consisting of at
maxFiles number of
• checkExtensionByMimeType:
most
le's MIME type.
uploaded les.
whether to check the le extension by the
If the extension produced by MIME type check
diers from the uploaded le extension, the le will be considered as
invalid. Defaults to true, meaning perform such check.
FileValidator
is used together with
yii\web\UploadedFile.
Please refer to
the Uploading Files section for complete coverage about uploading les and
performing validation about the uploaded les.
14.3.10 filter
[
]
// 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 lter on the input
value and assigns it back to the attribute being validated.
• filter:
a PHP callback that denes a lter.
function name, an anonymous function, etc.
must be
be set.
This can be a global
The function signature
function ($value) { return $newValue; }.
This property must
436
CHAPTER 14.
• skipOnArray:
SPECIAL TOPICS
whether to skip the lter if the input value is an array.
Defaults to false. Note that if the lter 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 the
trim validator.
Tip: There are many PHP functions that have the signature expected for the
filter
callback. For example to apply type casting
7
8
(using e.g. intval , boolval , . . . ) to ensure a specic type for an
attribute, you can simply specify the function names of the lter
without the need to wrap them in a closure:
['property', 'filter', 'filter' => 'boolval'],
['property', 'filter', 'filter' => 'intval'],
14.3.11 image
[
// checks if "primaryImage" is a valid image with proper size
['primaryImage', 'image', 'extensions' => 'png, jpg',
'minWidth' => 100, 'maxWidth' => 1000,
'minHeight' => 100, 'maxHeight' => 1000,
],
]
This validator checks if the input value represents a valid image le.
It
extends from the le validator and thus inherits all its properties. Besides,
it supports the following additional properties specic 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.3.12 in
[
// checks if "level" is 1, 2 or 3
['level', 'in', 'range' => [1, 2, 3]],
]
7
8
http://php.net/manual/en/function.intval.php
http://php.net/manual/en/function.boolval.php
14.3.
CORE VALIDATORS
437
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.3.13 integer
[
]
// checks if "age" is an integer
['age', 'integer'],
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.3.14 match
[
]
// 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 specied regular expression.
• 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.3.15 number
438
[
]
CHAPTER 14.
SPECIAL TOPICS
// 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.3.16 required
[
]
// checks if both "username" and "password" are not empty
[['username', 'password'], 'required'],
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
requiredValue is set, the comparison between
requiredValue will also check data types if this property
or not. When
the input
and
is true.
Info: How to determine if a value is empty or not is a separate
topic covered in the Empty Values section.
14.3.17 safe
[
]
// 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.3.18 string
[
]
// checks if "username" is a string whose length is between 4 and 24
['username', 'string', 'length' => [4, 24]],
14.3.
CORE VALIDATORS
439
This validator checks if the input value is a valid string with certain length.
• length:
species the length limit of the input string being validated.
This can be specied 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.
• encoding:
the encoding of the input string to be validated. If not set,
it will use the application's
charset
value which defaults to
UTF-8.
14.3.19 trim
[
]
// 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.3.20 unique
[
// 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']],
440
CHAPTER 14.
SPECIAL TOPICS
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
are to be validated. If the key and the value are the same, you can just
specify the value.
• filter:
additional lter 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
where()
yii\db\Query::
on the format of query condition), or an anonymous function
with the signature
function ($query),
where
$query
is the
Query
object
that you can modify in the function.
14.3.21 url
[
]
// 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.
https
Defaults to
['http', 'https'],
meaning both
http
and
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
extension, otherwise an exception would be thrown.
14.4 Internationalization
Note: This section is under development.
intl
PHP
14.4.
INTERNATIONALIZATION
441
Internationalization (I18N) refers to the process of designing a software application 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 oers several tools that help with internationalization of a website
such as message translation and number- and date-formatting.
14.4.1 Locale and Language
There are two languages dened in the Yii application:
and
target language.
source language
The source language is the language in which the original application
messages are written 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 current page, i.e. the language that original messages need to be translated to.
It is dened in the application conguration like the following:
return [
'id' => 'applicationID',
'basePath' => dirname(__DIR__),
// ...
'language' => 'ru-RU', // <- here!
// ...
]
Tip:
The default value for the
source language
it is recommended to keep this value.
is English and
The reason is that it's
easier to nd people translating from English to any language
than from non-English to non-English.
You may set the application language at runtime to the language that the
user has chosen. This has to be done at a point before any output is generated
so that it aects 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
9 and CC is the country
lowercase code for a language according to ISO-639
code according to ISO-3166
Note:
10 .
For more information on the concept and syntax of locales,
check the documentation of the ICU project
9
11 .
http://www.loc.gov/standards/iso639-2/
http://www.iso.org/iso/en/prods-services/iso3166ma/
02iso-3166-code-lists/list-en1.html
11
http://userguide.icu-project.org/locale#TOC-The-Locale-Concept
10
442
CHAPTER 14.
SPECIAL TOPICS
14.4.2 Message translation
Message translation is used to translate the messages that are output by an
application to dierent languages so that users from dierent countries can
use the application in their native language.
The message translation feature in Yii works simply as nding a translation 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 rst parameter of this method takes a
category which helps to distinguish the source of messages in dierent 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
language
from one of the message sources dened in the
i18n
application
application
component. A message source is a set of les or a database that provides
translation messages.
The following conguration example denes a mes-
sages source that takes the messages from PHP les:
'components' => [
// ...
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
//'basePath' => '@app/messages',
//'sourceLanguage' => 'en-US',
'fileMap' => [
'app' => 'app.php',
'app/error' => 'error.php',
],
],
],
],
],
In the above
app*
is a pattern that species which categories are handled
by the message source. In this case we're handling everything that begins
with
app.
Message les are located in
in your application directory. The
@app/messages,
fileMap
the
messages
directory
array denes which le is to be
used for which category. Instead of conguring
fileMap
you can rely on the
convention which is to use the category name as the le name (e.g. category
app/error
app/error.php under the basePath.
\Yii::t('app', 'This is a string to
language being ru-RU, Yii will rst look for
will result in the le name
When translating the message for
translate!') with the application
@app/messages/ru-RU/app.php to retrieve the list of available translations.
If there is no such le under ru-RU, it will try ru as well before failing.
a le
Beside storing the messages in PHP les (using
provides two other classes:
PhpMessageSource),
Yii
14.4.
INTERNATIONALIZATION
• yii\i18n\GettextMessageSource
443
that uses GNU Gettext MO or PO
les.
• yii\i18n\DbMessageSource
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 to keep the message strings meaningful and avoid using
too many positional parameters. Remember that the translator
has only the source string, so it should be obvious about what
will replace each placeholder.
Advanced placeholder formatting
In order to use the advanced features you need to install and enable the intl
PHP extension
12 . After installing and enabling it you will be able to use the
extended syntax for placeholders: either the short form
{placeholderName,
{placeholderName,
argumentType} that uses the default style, or the full form
argumentType, argumentStyle} that allows you to specify the
formatting style.
13 but we will show
A complete reference is available at the ICU website
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:
12
13
http://www.php.net/manual/en/intro.intl.php
http://icu-project.org/apiref/icu4c/classMessageFormat.html
444
CHAPTER 14.
SPECIAL TOPICS
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number, ,000,000000}', $sum);
14 .
Formatting reference
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());
15 .
Formatting reference
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());
16 .
Formatting reference
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!.
14
http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html
http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html
16
http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html
15
14.4.
INTERNATIONALIZATION
Plurals
445
Dierent languages have dierent ways to inect plurals. Yii pro-
vides a convenient way for translating messages in dierent plural forms that
works well even for very complex rules. Instead of dealing with the inection
rules directly, it is sucient to provide the translation of inected words in
certain situations only.
echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{
are # cats}}!', ['n' => $n]);
Will give us
$n = 0,
There is one cat! for $n = 1,
and There are 42 cats! for $n = 42.
In the plural rule arguments above, =0 means exactly zero, =1 stands for
exactly one, and other is for any other number. # is replaced with the value
of n. It's not that simple for languages other than English. Here's an example
•
•
•
There are no cats! for
for Russian:
Çäåñü
{n, plural, êîòîâ=0{ íåò} åñòü=1{ îäèí êîò} one{# êîò} few{# êîòà} many{#
êîòîâ} other{# êîòà}}!
In the above it's worth mentioning that
matches
21
or
=1
matches exactly
while
one
101.
Note, that you can not use the Russian example in
your
n = 1
source language isn't set to ru-RU.
Yii::t()
directly if
This however is not recommended,
instead such strings should go into message les or message database (in
case DB source is used). Yii uses the plural rules of the translated language
strings and is falling back to the plural rules of the source language if the
translation isn't available.
To learn which inection forms you should specify for your language, you
17 .
can refeer to the rules reference at unicode.org
Selections
You can select phrases based on keywords. The pattern in this
case species how to map keywords to phrases and provides a default phrase.
echo \Yii::t('app', '{name} is a {gender} and {gender, select, female{she}
male{he} other{it}} loves Yii!', [
'name' => 'Snoopy',
'gender' => 'dog',
]);
Will produce Snoopy is a dog and it loves Yii!.
In the expression above,
female
handles values that do not match.
and
male
are possible values, while
other
A string inside the brackets is a sub-
expression, so it could be a plain string or a string with nested placeholders
in it.
17
http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_
plural_rules.html
446
CHAPTER 14.
SPECIAL TOPICS
Specifying default translation
You can specify default translations that will be used as a fallback for categories that don't match any other translation. This translation should be
marked with
*.
In order to do it add the following to the application cong:
//configure i18n component
'i18n' => [
'translations' => [
'*' => [
'class' => 'yii\i18n\PhpMessageSource'
],
],
],
Now you can use categories without conguring each one, which is similar to
Yii 1.1 behavior. Messages for the category will be loaded from a le under
the default translation
basePath
that is
@app/messages:
echo Yii::t('not_specified_category', 'message from unspecified category');
The message will be loaded from
@app/messages/<LanguageCode>/not_specified_category
.php.
Translating module messages
If you want to translate the messages for a module and avoid using a single
translation le for all the messages, you can do it like the following:
<?php
namespace app\modules\users;
use Yii;
class Module extends \yii\base\Module
{
public $controllerNamespace = 'app\modules\users\controllers';
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
Yii::$app->i18n->translations['modules/users/*'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en-US',
'basePath' => '@app/modules/users/messages',
'fileMap' => [
'modules/users/validation' => 'validation.php',
14.4.
INTERNATIONALIZATION
}
];
],
447
'modules/users/form' => 'form.php',
...
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 ltering
each category per needed le. Instead of using
fileMap,
you can simply use
the convention of the category mapping to the same named le. Now you
Module::t('validation', 'your custom validation message')
t('form', 'some form label') directly.
can use
or
Module::
Translating widgets messages
The same rule as applied for Modules above can be applied for widgets too,
for example:
<?php
namespace app\widgets\menu;
use yii\base\Widget;
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' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en-US',
'basePath' => '@app/widgets/menu/messages',
'fileMap' => [
'widgets/menu/messages' => 'messages.php',
],
];
448
CHAPTER 14.
SPECIAL TOPICS
}
public function run()
{
echo $this->render('index');
}
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 the convention of the category
mapping to the same named le. Now you can use
messages {messages}', ['{messages}' => 10])
Note:
Menu::t('messages', 'new
directly.
For widgets you also can use i18n views, with the same
rules as for controllers being applied to them too.
Translating framework messages
Yii comes with the default translation messages for validation errors and
some other strings. These messages are all in the category
yii.
Sometimes
you want to correct the default framework message translation for your application. In order to do so, congure the
i18n
application component like
the following:
'i18n' => [
'translations' => [
'yii' => [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en-US',
'basePath' => '@app/messages'
],
],
],
Now you can place your adjusted translations to
@app/messages/<language>/
yii.php.
Handling missing translations
Even if the translation is missing from the source, Yii will display 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
14.4.
INTERNATIONALIZATION
449
may need to perform some custom processing of the situation, when the requested translation is missing from the source. This can be achieved using
the
missingTranslation-event
of
yii\i18n\MessageSource.
For example, let us mark all the missing translations with something
notable so that they can be easily found at the page. First we need to setup
an event handler. This can be done in the application conguration:
'components' => [
// ...
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
'fileMap' => [
'app' => 'app.php',
'app/error' => 'error.php',
],
'on missingTranslation' => ['app\components\
TranslationEventHandler', 'handleMissingTranslation']
],
],
],
],
Now we need to implement our own event handler:
<?php
namespace app\components;
use yii\i18n\MissingTranslationEvent;
class TranslationEventHandler
{
public static function handleMissingTranslation(MissingTranslationEvent
$event) {
$event->translatedMessage = "@MISSING: {$event->category}.{$event->
message} FOR LANGUAGE {$event->language} @";
}
}
If
yii\i18n\MissingTranslationEvent::$translatedMessage is set by the
event handler it will be displayed as the translation result.
Note: each message source handles its missing translations separately. If you are using several message sources and wish them to
treat the missing translations in the same way, you should assign
the corresponding event handler to each of them.
Using the
message
command
Translations can be stored in
les] or to
database.
php files, [[yii\i18n\GettextMessageSource|.po
See specic classes for additional options.
450
CHAPTER 14.
SPECIAL TOPICS
First of all you need to create a cong le. Decide where you want to
store it and then issue the command
./yii message/config path/to/config.php
Open the created le and adjust the parameters to t your needs. Pay special
attention to:
• languages:
an array representing what languages your app should be
translated to;
• messagePath:
path where to store message les, which should match the
i18n`s basePath
parameter stated in cong.
Note that aliases are not supported here, they must be real path
relative to the cong le location
Once you're done with the cong le you can nally extract your messages
with the command
./yii message path/to/config.php
You will then nd your les (if you've choosen le based translations) in your
messagePath
directory.
14.4.3 Views
Instead of translating messages as described in the last section, you can
also use
i18n
in your views to provide support for dierent languages. For
example, if you have a view
views/site/index.php
and you want to create a
special version for the Russian language, you create a
ru-RU
folder under the
view path of the current controller/widget and put the le for the Russian
language as
views/site/ru-RU/index.php.
Yii will then load the le for the
current language if it exists and fall back to the original view le if none was
found.
Note:
If language is specied as
en-US
sponding views, Yii will try views under
and there are no corre-
en
before using original
ones.
14.4.4 Formatting Number and Date values
See the data formatter section for details.
14.4.5 Setting up your PHP environment
Yii uses the PHP intl extension
18 to provide most of its internationalization
features such as the number and date formatting of the
18
http://php.net/manual/en/book.intl.php
yii\i18n\Formatter
14.5.
MAILING
451
class and the message formatting using
yii\i18n\MessageFormatter.
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 available with the PHP intl extension, so its installation
is highly recommended.
The PHP intl extension
19 is based on the ICU library20 which provides
the knowledge and formatting rules for all the dierent locales. According to
this fact the formatting of dates and numbers and also the supported syntax
available for message formatting diers between dierent 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 nd 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
See
#
http://site.icu-project.org/download
placeholder in plural rules.
for a list of available ICU
versions. Note that the version numbering has changed after the 4.8 release
so that the rst digits are now merged: the sequence is ICU 4.8, ICU 49,
ICU 50.
Additionally the information in the time zone database shipped with the
21 for details
ICU library may be outdated. Please refer to the ICU manual
on updating the time zone database. While for output formatting the ICU
timezone database is used, the time zone database used by PHP may be
relevant too. You can update it by installing the latest version of the pecl
package
timezonedb22 .
14.5 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
19
http://php.net/manual/en/book.intl.php
http://site.icu-project.org/
21
http://userguide.icu-project.org/datetime/timezone#
TOC-Updating-the-Time-Zone-Data
22
http://pecl.php.net/package/timezonedb
20
452
CHAPTER 14.
SPECIAL TOPICS
interface. Actual mail sending mechanism should be provided by the extension, because dierent projects may require its dierent implementation and
it usually depends on the external services and libraries.
For the most common cases you can use yii2-swiftmailer
23 ocial exten-
sion.
14.5.1 Conguration
Mail component conguration depends on the extension you have chosen. In
general your application conguration should look like:
return [
//....
'components' => [
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
],
];
14.5.2 Basic usage
Once the `mailer' component is congured, 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 the above example the 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)
}
$message->setTo(Yii::$app->params['adminEmail'])
->setSubject('Message subject')
->setTextBody('Plain text content')
->send();
23
https://github.com/yiisoft/yii2-swiftmailer
14.5.
MAILING
453
Note: each `mailer' extension comes in 2 major classes: `Mailer'
and `Message'. `Mailer' always knows the class name and specic
of the `Message'. Do not attempt to 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 benet from this approach, using single
network message etc.
14.5.3 Composing mail content
Yii allows composition of the actual mail messages content via special view
les. By default these les should be located at `@app/mail' path.
Example mail view le content:
<?php
use yii\helpers\Html;
use yii\helpers\Url;
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\BaseMessage instance of newly created mail
message */
?>
<h2>This message allows you to visit our site home page by one click</h2>
<?= Html::a('Go to home page', Url::home('http')) ?>
In order to compose message content via view le simply pass view name to
the
compose()
method:
Yii::$app->mailer->compose('home-link') // a view rendering result becomes
the message body here
->setFrom('
[email protected]')
->setTo('
[email protected]')
->setSubject('Message subject')
->send();
You may pass additional view parameters to
be available inside the view les:
Yii::$app->mailer->compose('greetings', [
'user' => Yii::$app->user->identity,
'advertisement' => $adContent,
]);
compose()
method, which will
454
CHAPTER 14.
SPECIAL TOPICS
You can specify dierent view les for HTML and plain text message contents:
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 can be setup
yii\mail\BaseMailer::$htmlLayout and yii\mail\BaseMailer::
$textLayout. It will work the same way like layouts in regular web apusing
plication.
Layout can be used to setup mail CSS styles or other shared
content:
<?php
use yii\helpers\Html;
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface 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.5.4 File attachment
You can add attachments to message using methods
():
attach() and attachContent
14.5.
MAILING
455
$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.5.5 Embedding 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 le:
Yii::$app->mailer->compose('embed-email', ['imageFileName' => '/path/to/
image.jpg'])
// ...
->send();
Then inside the view le you can use the following code:
<img src="<?= $message->embed($imageFileName); ?>">
14.5.6 Testing and debugging
A developer often has to check, what actual emails are sent by the application, what was their content and so on. Such ability is granted by Yii via
yii\mail\BaseMailer::useFileTransport.
If enabled, this option enforces saving
mail message data into the local les instead of regular sending. These les
will be saved under
yii\mail\BaseMailer::fileTransportPath,
which is `@run-
time/mail' by default.
Note: you can either save the messages to the les or send them
to the actual recipients, but can not do both simultaneously.
A mail message le can be opened by a regular text le editor, so you can
browse the actual message headers, content and so on. This mechanism may
prove itself, while debugging application or running unit test.
Note:
the mail message le content is composed via
\MessageInterface::toString(),
\yii\mail
so it depends on the actual mail
extension you are using in your application.
14.5.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
456
use
CHAPTER 14.
yii\mail\BaseMailer
and
yii\mail\BaseMessage
SPECIAL TOPICS
as the base classes for your
solution. These classes already contain the basic logic, which is described in
this guide. However, their usage is not mandatory, it is enough to implement
yii\mail\MailerInterface
and
yii\mail\MessageInterface
interfaces. Then you
need to implement all the abstract methods to build your solution.
14.6 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
ne-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.6.1 Preparing environment
A well congured environment to run PHP application really matters.
In
order to get maximum performance:
•
Always use the latest stable PHP version. Each major release brings
signicant performance improvements and reduced memory usage.
•
24 for PHP 5.4 and less or Opcache25 for PHP 5.5 and more.
Use APC
It gives a very good performance boost.
14.6.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 dened as true in index.php. So,
index.php:
in order to disable
debug mode, the following should be in your
defined('YII_DEBUG') or define('YII_DEBUG', false);
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.
24
25
http://ru2.php.net/apc
http://php.net/opcache
14.6.
PERFORMANCE TUNING
457
Enabling PHP opcode cache
Enabling the PHP opcode cache improves any PHP application performance
and lowers memory usage signicantly. Yii is no exception. It was tested with
both PHP 5.5 OPcache
26 and APC PHP extension27 . Both cache 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
Connection::enableSchemaCache
conguration config/web.php:
setting the
property to be
true
via application
return [
// ...
'components' => [
// ...
'db' => [
'class' => 'yii\db\Connection',
'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' => 'yii\caching\FileCache',
],
Note that the
cache
application component should be congured.
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 experience for end user of your application.
In order to learn how it can be achieved, refer to assets guide section.
26
27
http://php.net/manual/en/book.opcache.php
http://php.net/manual/en/book.apc.php
458
CHAPTER 14.
SPECIAL TOPICS
Using better storage for sessions
By default PHP uses les 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.
conguring your application via
You can do so by
config/web.php:
return [
// ...
'components' => [
'session' => [
'class' => 'yii\web\DbSession',
// Set the following if you want to use DB component other than
// default 'db'.
// 'db' => 'mydb',
];
],
],
You can use
// To override default session table, set the following
// 'sessionTable' => 'my_session',
CacheSession
to store sessions using cache. Note that some cache
storage such as memcached has no guarantee that session data will not be
lost, and it would lead to unexpected logouts.
If you have Redis
28 on your server, it's highly recommended as session
storage.
14.6.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 signicantly. If the
generation of some data takes long time, we can use the data caching approach 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.
Leveraging HTTP caching to save processing time and bandwidth
Leveraging HTTP caching saves both processing time, bandwidth and resources signicantly. It can be implemented by sending either
Modified
ETag
or
Last-
header in your application response. If browser is implemented ac-
cording to HTTP specication (most browsers are), content will be fetched
only if it is dierent from what it was prevously.
28
http://redis.io/
14.6.
PERFORMANCE TUNING
459
Forming proper headers is time consuming task so Yii provides a shortcut
in form of controller lter
yii\filters\HttpCache.
In a controller you need to implement
behaviors
Using it is very easy.
method like the following:
public function behaviors()
{
return [
'httpCache' => [
'class' => \yii\filters\HttpCache::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 modied in a
way that is cheaper than fetching and rendering that content.
lastModified
etagSeed
should return unix timestamp of the last content modication while
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 enormous 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 modeling data in an OOP fashion, it actually degrades performance due to the
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.
460
CHAPTER 14.
SPECIAL TOPICS
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 elds 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 specied and
you're working with AR objects.
Composer autoloader optimization
In order to improve overall performance you can execute
-o
composer dumpautoload
to optimize Composer autoloader.
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.
There are two common ways to achieve it: cron job processing and specialized queues.
In the rst case we need to save the data that we want to process later
to a persistent storage such as database.
A console command that is run
regularly via cron job queries database and processes data if there's any.
The solution is OK for most cases but has one signicant drawback. We
aren't aware if there's data to process before we query database, so we're
either querying database quite often or have a slight delay between each
data processing.
This issue could be solved by queue and job servers such RabbitMQ,
ActiveMQ, Amazon SQS and more.
In this case instead of writing data
to persistent storage you're queueing it via APIs provided by queue or job
server. Processing is often put into job handler class. Job from the queue is
executed right after all jobs before it are done.
14.7.
SHARED HOSTING ENVIRONMENT
461
If nothing helps
If nothing helps, never assume what may x performance problem. Always
prole your code instead before changing anything. The following tools may
be helpful:
•
•
•
Yii debug toolbar and debugger
29
XDebug proler
30
XHProf
14.7 Shared Hosting Environment
Shared hosting environments are often quite limited about conguration and
directory structure. Still in most cases you can run Yii 2.0 on a shared hosting
environment with a few adjustements.
14.7.1 Deploying a basic application
Since in a shared hosting environment there's typically only one webroot,
use the basic project template if you can. Refer to the Installing Yii chapter
and install the basic project template locally. After you have the application
working locally, we'll make some adjustments so it can be hosted on your
shared hosting server.
Renaming webroot
Connect to your shared host using FTP or by other means. You will probably
see something like the following.
config
logs
www
In the above,
www
is your webserver webroot directory.
dierently. Common names are:
www, htdocs,
and
It could be named
public_html.
web. Before uploading
The webroot in our basic project template is named
the application to your webserver rename your local webroot to match your
server, i.e., from
web
to
www, public_html
or whatever the name of your hosting
webroot.
FTP root directory is writeable
If you can write to the root level directory i.e. where
are, then upload
29
30
assets, commands
config, logs
and
etc. as is to the root level directory.
http://xdebug.org/docs/profiler
http://www.php.net/manual/en/book.xhprof.php
www
462
CHAPTER 14.
SPECIAL TOPICS
Add extras for webserver
If your webserver is Apache you'll need to add an
following content to
web
(or
public_html
.htaccess
le with the
or whatever) (where the
index.php
le is located):
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule . index.php
In case of nginx you should not need any extra cong les.
Check requirements
In order to run Yii, your webserver must meet its requirements. The very
minimum requirement is PHP 5.4. In order to check the requirements copy
requirements.php
from your root directory into the webroot directory and run
it via browser using
http://example.com/requirements.php
URL. Don't forget
to delete the le afterwards.
14.7.2 Deploying an advanced application
Deploying an advanced application to shared hosting is a bit trickier than
a basic application because it has two webroots, which shared hosting webservers don't support. We will need to adjust the directory structure.
Move entry scripts into single webroot
First of all we need a webroot directory. Create a new directory and name
it to match your hosting webroot name as described in Renaming webroot
above, e.g.,
where
www
www
www
or
public_html
or the like. Then create the following structure
is the hosting webroot directory you just created:
admin
backend
common
console
environments
frontend
...
14.8.
USING TEMPLATE ENGINES
463
www
will be our frontend directory so move the contents of
into
it.
Move the contents of
will
need to adjust the paths
frontend/web
backend/web into www/admin. In each case you
in index.php and index-test.php.
Separate sessions and cookies
Originally the backend and frontend are intended to run at dierent domains.
When we're moving it all to the same domain the frontend and backend will
be sharing the same cookies, creating a clash.
backend application cong
backend/config/main.php
It order to x it, adjust
as follows:
'components' => [
'request' => [
'csrfParam' => '_backendCSRF',
'csrfCookie' => [
'httpOnly' => true,
'path' => '/admin',
],
],
'user' => [
'identityCookie' => [
'name' => '_backendIdentity',
'path' => '/admin',
'httpOnly' => true,
],
],
'session' => [
'name' => 'BACKENDSESSID',
'cookieParams' => [
'path' => '/admin',
],
],
],
14.8 Using template engines
By default, Yii uses PHP as its template language, but you can congure
Yii to support other rendering engines, such as Twig
31 or Smarty32 available
as extensions.
The
view
component is responsible for rendering views. You can add a
custom template engine by reconguring this component's behavior:
[
'components' => [
'view' => [
'class' => 'yii\web\View',
'renderers' => [
31
32
http://twig.sensiolabs.org/
http://www.smarty.net/
464
CHAPTER 14.
],
]
],
],
SPECIAL TOPICS
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
//'cachePath' => '@runtime/Smarty/cache',
],
'twig' => [
'class' => 'yii\twig\ViewRenderer',
'cachePath' => '@runtime/Twig/cache',
// Array of twig options:
'options' => [
'auto_reload' => true,
],
'globals' => ['html' => '\yii\helpers\Html'],
'uses' => ['yii\bootstrap'],
],
// ...
In the code above, both Smarty and Twig are congured to be useable by
the view les. But in order to get these extensions into your project, you
need to also modify your
composer.json
le 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 le, you can install the extensions by
running
composer update --prefer-dist
in the command-line.
For details about using concrete template engine please refer to its documentation:
•
•
33
Twig guide
Smarty guide
34
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.
33
34
https://github.com/yiisoft/yii2-twig/tree/master/docs/guide
https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide
14.9.
WORKING WITH THIRD-PARTY CODE
465
Using Composer Packages
35 packages.
Many third-party libraries are released in terms of Composer
You can install such libraries by taking the following two simple steps:
1. modify the
composer.json
le of your application and specify which
Composer packages you want to install.
2. run
composer install
to install the specied 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 le manually and unpack it in the
BasePath
BasePath/vendor
directory, where
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
before you include the
Yii.php
le 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 follows PSR-4
36 , 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 directory
vendor/foo/bar,
and the library classes are under the
xyz root namespace.
You can include the following code in your application conguration:
[
'aliases' => [
'@xyz' => '@vendor/foo/bar',
],
]
If neither of the above is the case, it is likely that the library relies on PHP
include path conguration to correctly locate and include class les. Simply
follow its instruction on how to congure the PHP include path.
35
36
https://getcomposer.org/
http://www.php-fig.org/psr/psr-4/
466
CHAPTER 14.
SPECIAL TOPICS
In the worst case when the library requires explicitly including every class
le, you can use the following method to include the classes on demand:
•
•
Identify which classes the library contains.
List the classes and the corresponding le 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 systems, such as WordPress, Joomla, or applications developed using other
PHP frameworks.
\ArrayHelper
For example, you may want to use the
yii\helpers
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:
composer global require "fxp/composer-asset-plugin:1.0.0"
composer require yiisoft/yii2
composer install
37 which allows man-
The rst command installs the composer asset plugin
aging bower and npm package dependencies through Composer. Even if you
only want to use the database layer or other non-asset related features of
Yii, this is required to install the Yii composer package. See also the general
section about installing Yii for more information on Composer and solution
to possible issues popping up during the installation.
Otherwise, you can download
BasePath/vendor
38 the Yii release le and unpack it in the
directory.
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 yii\web\Application($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 dierence is that after the application
instance is created, the
run(),
run()
method is not called. This is because by calling
Yii will take over the control of the request handling workow which
is not needed in this case and already handled by the existing application.
37
38
https://github.com/francoispluchino/composer-asset-plugin/
http://www.yiiframework.com/download/
14.9.
WORKING WITH THIRD-PARTY CODE
467
Like in a Yii application, you should congure the application instance
based on the environment running the third-party system.
to use the Active Record feature, you need to congure the
For example,
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 yii\web\Application($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 cus-
tomized version to combine them. The above code includes the customized
Yii
class le, which can be created as follows.
$yii2path = '/path/to/yii2';
require($yii2path . '/BaseYii.php'); // Yii 2.x
$yii1path = '/path/to/yii1';
require($yii1path . '/YiiBase.php'); // Yii 1.x
class Yii extends \yii\BaseYii
{
// copy-paste the code from YiiBase (1.x) here
}
Yii::$classMap = include($yii2path . '/classes.php');
// register Yii2 autoloader via Yii1
Yii::registerAutoloader(['Yii', 'autoload']);
// create the dependency injection container
Yii::$container = new yii\di\Container;
468
CHAPTER 14.
SPECIAL TOPICS
Yii::$app to access the
Yii::app() will give you the Yii 1 application
That's all! Now in any part of your code, you can use
Yii 2 application instance, while
instance:
echo get_class(Yii::app()); // outputs 'CWebApplication'
echo get_class(Yii::$app); // outputs 'yii\web\Application'
Chapter 15
Widgets
469
470
CHAPTER 15.
WIDGETS
Error: not existing le: https://github.com/yiisoft/yii2-bootstrap/blob/master/d
471
Error: not existing le: https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/REA
472
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.
helper classes are organized under the
yii\helpers
These
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 yii\helpers\Html;
echo Html::encode('Test > test');
Note: To support customizing helper classes, Yii breaks each core
helper class into two classes: a base class (e.g.
and a concrete class (e.g.
ArrayHelper).
BaseArrayHelper)
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
HtmlPurier
Image
Inector
473
474
CHAPTER 16.
•
•
•
•
•
•
HELPERS
Json
Markdown
Security
StringHelper
Url
VarDumper
16.1.2 Customizing Helper Classes
To customize a core helper class (e.g.
yii\helpers\ArrayHelper),
you
should create a new class extending from the helpers corresponding base
class (e.g.
yii\helpers\BaseArrayHelper) and name your class the same
yii\helpers\ArrayHelper), in-
as the corresponding concrete class (e.g.
cluding its namespace. This class will then be set up to replace the original
implementation of the framework.
The following example shows how to customize the
the
yii\helpers\ArrayHelper
merge()
method of
class:
<?php
namespace yii\helpers;
class ArrayHelper extends BaseArrayHelper
{
public static function merge($a, $b)
{
// your custom implementation
}
}
ArrayHelper.php.
@app/components.
Save your class in a le named
directory, for example
The le can be in any
Next, in your application's entry script, add the following line of code
after including the
yii.php
le to tell the Yii class autoloader to load your
custom class instead of the original helper class from the framework:
Yii::$classMap['yii\helpers\ArrayHelper'] = '@app/components/ArrayHelper.php
';
Note that customizing of helper classes is only useful if you want to change
the behavior of an existing function of the helpers. If you want to add additional functions to use in your application you may better create a separate
helper for that.
16.2 ArrayHelper
1
Additionally to the rich set of PHP array functions , the Yii array helper
provides extra static methods allowing you to deal with arrays more e-
1
http://php.net/manual/en/book.array.php
16.2.
ARRAYHELPER
475
ciently.
16.2.1 Getting Values
Retrieving values from an array, an object or a complex structure consisting
of both using standard PHP is quite repetitive.
exists with
isset
You have to check if key
rst, then if it does you're getting it, if not, providing
default value:
class User
{
public $name = 'Alex';
}
$array = [
'foo' => [
'bar' => new User(),
]
];
$value = isset($array['foo']['bar']->name) ? $array['foo']['bar']->name :
null;
Yii provides a very convenient method to do it:
$value = ArrayHelper::getValue($array, 'foo.bar.name');
First method argument is where we're getting value from. Second argument
species how to get the data. It could be one of the following:
•
•
Name of array key or object property to retrieve value from.
Set of dot separated array keys or object property names.
The one
we've used in the example above.
•
A callback returning a value.
The callback should be the following:
$fullName = ArrayHelper::getValue($user, function ($user, $defaultValue) {
return $user->firstName . ' ' . $user->lastName;
});
Third optional argument is default value which is
null
if not specied. Could
be used as follows:
$username = ArrayHelper::getValue($comment, 'user.username', 'Unknown');
In case you want to get the value and then immediately remove it from array
you can use
remove
method:
$array = ['type' => 'A', 'options' => [1, 2]];
$type = ArrayHelper::remove($array, 'type');
After executing the code
will be
A.
names only.
$array will contain ['options' => [1, 2]] and $type
getValue method, remove supports simple key
Note that unlike
476
CHAPTER 16.
HELPERS
16.2.2 Checking Existence of Keys
ArrayHelper::keyExists works the same way as array_key_exists2
except that
it also supports case-insensitive key comparison. For example,
$data1 = [
'userName' => 'Alex',
];
$data2 = [
'username' => 'Carsten',
];
if (!ArrayHelper::keyExists('username', $data1, false) || !ArrayHelper::
keyExists('username', $data2, false)) {
echo "Please provide username.";
}
16.2.3 Retrieving Columns
Often you need to get a column of values from array of data rows or objects.
Common example is getting a list of IDs.
$data = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
];
$ids = ArrayHelper::getColumn($array, 'id');
The result will be
['123', '345'].
If additional transformations are required or the way of getting value is
complex, second argument could be specied as an anonymous function:
$result = ArrayHelper::getColumn($array, function ($element) {
return $element['id'];
});
16.2.4 Re-indexing Arrays
In order to index an array according to a specied key, the
index
method can
be used. The input array should be multidimensional or an array of objects.
The key can be a key name of the sub-array, a property name of object, or
an anonymous function which returns the key value given an array element.
If a key value is null, the corresponding array element will be discarded
and not put in the result. For example,
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
];
2
http://php.net/manual/en/function.array-key-exists.php
16.2.
ARRAYHELPER
477
$result = ArrayHelper::index($array, 'id');
// the result is:
// [
//
'123' => ['id' => '123', 'data' => 'abc'],
//
'345' => ['id' => '345', 'data' => 'def'],
// ]
// using anonymous function
$result = ArrayHelper::index($array, function ($element) {
return $element['id'];
});
16.2.5 Building Maps
In order to build a map (key-value pairs) from a multidimensional array or
an array of objects you can use
map
method. The
$from
and
$to
parameters
specify the key names or property names to set up the map. Optionally, one
can further group the map according to a grouping eld
$group.
For example,
$array = [
['id' => '123', 'name' => 'aaa', 'class' => 'x'],
['id' => '124', 'name' => 'bbb', 'class' => 'x'],
['id' => '345', 'name' => 'ccc', 'class' => 'y'],
);
$result = ArrayHelper::map($array, 'id', 'name');
// the result is:
// [
//
'123' => 'aaa',
//
'124' => 'bbb',
//
'345' => 'ccc',
// ]
$result = ArrayHelper::map($array, 'id', 'name', 'class');
// the result is:
// [
//
'x' => [
//
'123' => 'aaa',
//
'124' => 'bbb',
//
],
//
'y' => [
//
'345' => 'ccc',
//
],
// ]
16.2.6 Multidimensional Sorting
multisort
method helps to sort an array of objects or nested arrays by one
or several keys. For example,
$data = [
['age' => 30, 'name' => 'Alexander'],
478
CHAPTER 16.
HELPERS
['age' => 30, 'name' => 'Brian'],
['age' => 19, 'name' => 'Barney'],
];
ArrayHelper::multisort($data, ['age', 'name'], [SORT_ASC, SORT_DESC]);
After sorting we'll get the following in
[
$data:
['age' => 19, 'name' => 'Barney'],
['age' => 30, 'name' => 'Brian'],
['age' => 30, 'name' => 'Alexander'],
];
Second argument that species keys to sort by can be a string if it's a single
key, an array in case of multiple keys or an anonymous function like the
following one:
ArrayHelper::multisort($data, function($item) {
return isset($item['age']) ? ['age', 'name'] : 'name';
});
Third argument is direction. In case of sorting by a single key it could be
either
SORT_ASC
or
SORT_DESC.
If sorting by multiple values you can sort each
value dierently by providing an array of sort direction.
Last argument is PHP sort ag that could take the same values as the
3
ones passed to PHP sort() .
16.2.7 Detecting Array Types
It is handy to know whether an array is indexed or an associative. Here's an
example:
// no keys specified
$indexed = ['Qiang', 'Paul'];
echo ArrayHelper::isIndexed($indexed);
// all keys are strings
$associative = ['framework' => 'Yii', 'version' => '2.0'];
echo ArrayHelper::isAssociative($associative);
16.2.8 HTML Encoding and Decoding Values
In order to encode or decode special characters in an array of strings into
HTML entities you can use the following:
$encoded = ArrayHelper::htmlEncode($data);
$decoded = ArrayHelper::htmlDecode($data);
Only values will be encoded by default. By passing second argument as
false
you can encode array's keys as well. Encoding will use application charset
and could be changed via third argument.
3
http://php.net/manual/en/function.sort.php
16.2.
ARRAYHELPER
479
16.2.9 Merging Arrays
/**
* Merges two or more arrays into one recursively.
* If each array has an element with the same string key value, the
latter
* will overwrite the former (different from array_merge_recursive).
* Recursive merging will be conducted if both arrays have an element of
array
* type and are having the same key.
* For integer-keyed elements, the elements from the latter array will
* be appended to the former array.
* @param array $a array to be merged to
* @param array $b array to be merged from. You can specify additional
* arrays via third argument, fourth argument etc.
* @return array the merged array (the original arrays are not changed.)
*/
public static function merge($a, $b)
16.2.10 Converting Objects to Arrays
Often you need to convert an object or an array of objects into an array.
The most common case is converting active record models in order to serve
data arrays via REST API or use it otherwise. The following code could be
used to do it:
$posts = Post::find()->limit(10)->all();
$data = ArrayHelper::toArray($post, [
'app\models\Post' => [
'id',
'title',
// the key name in array result => property name
'createTime' => 'created_at',
// the key name in array result => anonymous function
'length' => function ($post) {
return strlen($post->content);
},
],
]);
The rst argument contains the data we want to convert. In our case we're
converting a
Post
AR model.
The second argument is conversion mapping per class. We're setting a
mapping for
Post
model. Each mapping array contains a set of mappings.
Each mapping could be:
•
•
A eld name to include as is.
A key-value pair of desired array key name and model column name
to take value from.
•
A key-value pair of desired array key name and a callback which returns
value.
The result of conversion above will be:
480
[
CHAPTER 16.
HELPERS
'id' => 123,
'title' => 'test',
'createTime' => '2013-01-01 12:00AM',
'length' => 301,
]
It is possible to provide default way of converting object to array for a specic
class by implementing
Arrayable
interface in that class.
16.3 Html helper
Every web application generates lots of HTML markup. If markup is static,
4 but
it could be done eciently by mixing PHP and HTML in a single le
when it is generated dynamically it is starting to get tricky to handle it
without extra help. Yii provides such help in a form of Html helper which
provides a set of static methods for handling commonly used HTML tags,
their options and content.
Note: If your markup is nearly static it's better to use HTML
directly.
There's no need to wrap absolutely everything with
Html helper calls.
16.3.1 Basics
Since building dynamic HTML by string concatenation is getting messy very
fast, Yii provides a set of methods to manipulate tag options and build tags
based on these options.
Generating Tags
The code generating a tag looks like the following:
<?= Html::tag('p', Html::encode($user->name), ['class' => 'username']) ?>
The rst argument is tag name. Second one is content to be enclosed between
the start and end tags. Note that we are using
Html::encode.
That's because
content isn't encoded automatically to allow using HTML when needed.
Third one is an array of HTML options or, in other words, tag attributes.
In this array key is the name of the attribute such as
class, href
and a value is its value.
The code above will generate the following HTML:
<p class="username">samdark</p>
4
http://php.net/manual/en/language.basic-syntax.phpmode.php
or
target
16.3.
HTML HELPER
481
In case you need just start tag or just closing tag you can use
and
Html::endTag()
Html::beginTag()
methods.
Options are used in many methods of Html helper and various widgets.
In all these cases there is some extra handling to know about:
•
•
If a value is null, the corresponding attribute will not be rendered.
Attributes whose values are of boolean type will be treated as boolean
5
attributes .
•
•
The values of attributes will be HTML-encoded using
Html::encode().
If the value of an attribute is an array, it will be handled as follows:
If the attribute is a data attribute as listed in
::$dataAttributes,
such as
data
or
ng,
yii\helpers\Html
a list of attributes will
be rendered, one for each element in the value array. For exam-
'data' => ['id' => 1, 'name' => 'yii'] generates data-id="1"
data-name="yii"; and 'data' => ['params' => ['id' => 1, 'name' =>
'yii'], 'status' => 'ok'] generates data-params='{"id":1,"name":
"yii"}' data-status="ok". Note that in the latter example, JSON
ple,
format is used to render a sub-array.
If the attribute is NOT a data attribute, the value will be JSON-
['params' => ['id' => 1, 'name' => 'yii']
params='{"id":1,"name":"yii"}'.
encoded. For example,
generates
Forming CSS Classes and Styles
When building options for HTML tag we're often starting with defaults
which we need to modify. In order to add or remove CSS class you can use
the following:
$options = ['class' => 'btn btn-default'];
if ($type === 'success') {
Html::removeCssClass($options, 'btn-default');
Html::addCssClass($options, 'btn-success');
}
echo Html::tag('div', 'Pwede na', $options);
// in case of $type of 'success' it will render
// <div class="btn btn-success">Pwede na</div>
In order to do the same with styles for the
style
attribute:
$options = ['style' => ['width' => '100px', 'height' => '100px']];
// gives style="width: 100px; height: 200px; position: absolute;"
Html::addCssStyle($options, 'height: 200px; position: absolute;');
// gives style="position: absolute;"
Html::removeCssStyle($options, ['width', 'height']);
5
http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes
482
CHAPTER 16.
When using
addCssStyle()
HELPERS
you can specify either an array of key-value
pairs corresponding to CSS property names and values or a string such
as
width: 100px; height: 200px;.
and forth by using
removeCssStyle()
These formats could be converted there
cssStyleFromArray()
and
cssStyleToArray().
The
method accepts an array of properties to remove. If it's
going to be a single property it could be specied as string.
Encoding and Decoding Content
In order for content to be displayed properly and securely in HTML special
characters in the content should be encoded. In PHP it's done with htmlspecialchars
6 and htmlspecialchars_decode7 .
The issue with using these
methods directly is that you have to specify encoding and extra ags all the
time. Since ags are the same all the time and encoding should match the
one of the application in order to prevent security issues, Yii provides two
compact and simple to use methods:
$userName = Html::encode($user->name);
echo $userName;
$decodedUserName = Html::decode($userName);
16.3.2 Forms
Dealing with forms markup is quite repetitive and error prone. Because of
that there is a group of methods to help dealing with them.
Note: consider using
ActiveForm
in case you deal with models
and need validation.
Creating Forms
Form could be opened with
beginForm()
method like the following:
<?= Html::beginForm(['order/update', 'id' => $id], 'post', ['enctype' => '
multipart/form-data']) ?>
First argument is the URL form will be submitted to. It could be specied
in form of Yii route and parameters accepted by
method to use.
tag.
post
Url::to().
Second one is
is default. Third one is array of options for the form
In this case we're changing the way of encoding form data in POST
request to
multipart/form-data.
It is required in order to upload les.
Closing form tag is simple:
<?= Html::endForm() ?>
6
7
http://www.php.net/manual/en/function.htmlspecialchars.php
http://www.php.net/manual/en/function.htmlspecialchars-decode.php
16.3.
HTML HELPER
483
Buttons
In order to generate buttons you can use the following code:
<?= Html::button('Press me!', ['class' => 'teaser']) ?>
<?= Html::submitButton('Submit', ['class' => 'submit']) ?>
<?= Html::resetButton('Reset', ['class' => 'reset']) ?>
First argument for all three methods is button title and the second one is
options. Title isn't encoded so if you're getting data from end user, encode
it with
Html::encode().
Input Fields
There are two groups on input methods. The ones starting with
called active inputs and the ones not starting with it.
active
and
Active inputs are
taking data from model and attribute specied while in case of regular input
data is specied directly.
The most generic methods are:
type, input name, input value, options
<?= Html::input('text', 'username', $user->name, ['class' => $username]) ?>
type, model, model attribute name, options
<?= Html::activeInput('text', $user, 'name', ['class' => $username]) ?>
If you know input type in advance it's more convenient to use shortcut
methods:
•
•
•
•
•
•
•
•
yii\helpers\Html::buttonInput()
yii\helpers\Html::submitInput()
yii\helpers\Html::resetInput()
yii\helpers\Html::textInput(), yii\helpers\Html::activeTextInput()
yii\helpers\Html::hiddenInput(), yii\helpers\Html::activeHiddenInput()
yii\helpers\Html::passwordInput() / yii\helpers\Html::activePasswordInput()
yii\helpers\Html::fileInput(), yii\helpers\Html::activeFileInput()
yii\helpers\Html::textarea(), yii\helpers\Html::activeTextarea()
Radios and checkboxes are a bit dierent in terms of method signature:
<?= Html::radio('agree', true, ['label' => 'I agree']);
<?= Html::activeRadio($model, 'agree', ['class' => 'agreement'])
<?= Html::checkbox('agree', true, ['label' => 'I agree']);
<?= Html::activeCheckbox($model, 'agree', ['class' => 'agreement'])
Dropdown list and list box could be rendered like the following:
<?= Html::dropDownList('list', $currentUserId, ArrayHelper::map($userModels,
'id', 'name')) ?>
<?= Html::activeDropDownList($users, 'id', ArrayHelper::map($userModels, 'id
', 'name')) ?>
<?= Html::listBox('list', $currentUserId, ArrayHelper::map($userModels, 'id'
, 'name')) ?>
484
CHAPTER 16.
HELPERS
<?= Html::activeListBox($users, 'id', ArrayHelper::map($userModels, 'id', '
name')) ?>
First argument is the name of the input, second is the value that's currently
selected and third is key-value pairs where array key is list value and array
value is list label.
If you want multiple choices to be selectable, checkbox list is a good
match:
<?= Html::checkboxList('roles', [16, 42], ArrayHelper::map($roleModels, 'id'
, 'name')) ?>
<?= Html::activeCheckboxList($user, 'role', ArrayHelper::map($roleModels, '
id', 'name')) ?>
If not, use radio list:
<?= Html::radioList('roles', [16, 42], ArrayHelper::map($roleModels, 'id', '
name')) ?>
<?= Html::activeRadioList($user, 'role', ArrayHelper::map($roleModels, 'id',
'name')) ?>
Labels and Errors
Same as inputs there are two methods for generating form labels.
Active
that's taking data from the model and non-active that accepts data directly:
<?= Html::label('User name', 'username', ['class' => 'label username']) ?>
<?= Html::activeLabel($user, 'username', ['class' => 'label username'])
In order to display form errors from a model or models as a summary you
could use:
<?= Html::errorSummary($posts, ['class' => 'errors']) ?>
To display individual error:
<?= Html::error($post, 'title', ['class' => 'error']) ?>
Input Names and Values
There are methods to get names, ids and values for input elds based on the
model. These are mainly used internally but could be handy sometimes:
// Post[title]
echo Html::getInputName($post, 'title');
// post-title
echo Html::getInputId($post, 'title');
// my first post
echo Html::getAttributeValue($post, 'title');
// $post->authors[0]
echo Html::getAttributeValue($post, '[0]authors[0]');
16.3.
HTML HELPER
485
In the above rst argument is the model while the second one is attribute
expression. In its simplest form it's attribute name but it could be an attribute name prexed and/or suxed with array indexes which are mainly
used for tabular input:
• [0]content
is used in tabular data input to represent the content at-
tribute for the rst model in tabular input;
• dates[0] represents the rst array element of the
• [0]dates[0] represents the rst array element of
dates attribute;
the dates attribute
for the rst model in tabular input.
In order to get attribute name without suxes or prexes one can use the
following:
// dates
echo Html::getAttributeName('dates[0]');
16.3.3 Styles and Scripts
There two methods to generate tags wrapping embedded styles and scripts:
<?= Html::style('.danger { color: #f00; }') ?>
Gives you
<style>.danger { color: #f00; }</style>
<?= Html::script('alert("Hello!");', ['defer' => true]);
Gives you
<script defer>alert("Hello!");</script>
If you want to link external style from CSS le:
<?= Html::cssFile('@web/css/ie5.css', ['condition' => 'IE 5']) ?>
generates
<!--[if IE 5]>
<link href="http://example.com/css/ie5.css" />
<![endif]-->
First argument is the URL. Second is an array of options. Additionally to
regular options you could specify:
• condition to wrap <link with conditional comments with condition specied. Hope you won't need conditional comments ever ;)
• noscript
could be set to
true
to wrap
<link
with
<noscript>
tag so it
will be included only when there's either no JavaScript support in the
browser or it was disabled by the user.
To link JavaScript le:
486
CHAPTER 16.
HELPERS
<?= Html::jsFile('@web/js/main.js') ?>
Same as with CSS rst argument species link to the le to be included.
Options could be passed as the second argument. In options you can specify
condition
in the same way as in options for
cssFile.
16.3.4 Hyperlinks
There's a method to generate hyperlink conveniently:
<?= Html::a('Profile', ['user/view', 'id' => $id], ['class' => 'profile-link
']) ?>
The rst argument is the title. It's not encoded so if you're using data got
from user you need to encode it with
what will be in
href
of
<a
tag.
Html::encode().
Second argument is
See Url::to() for details on what values it
accepts. Third argument is array of tag properties.
In you need to generate
mailto
link you can use the following code:
<?= Html::mailto('Contact us', '
[email protected]') ?>
16.3.5 Images
In order to generate image tag use the following:
<?= Html::img('@web/images/logo.png', ['alt' => 'My logo']) ?>
generates
<img src="http://example.com/images/logo.png" alt="My logo" />
Aside aliases the rst argument can accept routes, parameters and URLs.
Same way as Url::to() does.
16.3.6 Lists
Unordered list could be generated like the following:
<?= Html::ul($posts, ['item' => function($item, $index) {
return Html::tag(
'li',
$this->render('post', ['item' => $item]),
['class' => 'post']
);
}]) ?>
In order to get ordered list use
Html::ol()
instead.
16.4 Url Helper
Url helper provides a set of static methods for managing URLs.
16.4.
URL HELPER
487
16.4.1 Getting Common URLs
There are two methods you can use to get common URLs: home URL and
base URL of the current request. In order to get home URL, use the following:
$relativeHomeUrl = Url::home();
$absoluteHomeUrl = Url::home(true);
$httpsAbsoluteHomeUrl = Url::home('https');
If no parameter is passed, the generated URL is relative.
pass
true
You can either
to get an absolute URL for the current schema or specify a schema
explicitly (https,
http).
To get the base URL of the current request use the following:
$relativeBaseUrl = Url::base();
$absoluteBaseUrl = Url::base(true);
$httpsAbsoluteBaseUrl = Url::base('https');
The only parameter of the method works exactly the same as for
Url::home().
16.4.2 Creating URLs
In order to create a URL to a given route use the
The method uses
yii\web\UrlManager
Url::toRoute()
method.
to create a URL:
$url = Url::toRoute(['product/view', 'id' => 42]);
You may specify the route as a string, e.g.,
site/index.
You may also use an
array if you want to specify additional query parameters for the URL being
created. The array format must be:
// generates: /index.php?r=site/index¶m1=value1¶m2=value2
['site/index', 'param1' => 'value1', 'param2' => 'value2']
If you want to create a URL with an anchor, you can use the array format
with a
#
parameter. For example,
// generates: /index.php?r=site/index¶m1=value1#name
['site/index', 'param1' => 'value1', '#' => 'name']
A route may be either absolute or relative. An absolute route has a leading
slash (e.g.
or
index).
/site/index)
while a relative route has none (e.g.
site/index
A relative route will be converted into an absolute one by the
following rules:
•
•
If the route is an empty string, the current
If the route contains no slashes at all (e.g.
route
will be used;
index),
it is considered to
be an action ID of the current controller and will be prepended with
yii\web\Controller::$uniqueId;
•
If the route has no leading slash (e.g.
site/index),
it is considered to
be a route relative to the current module and will be prepended with
the module's
uniqueId.
488
CHAPTER 16.
HELPERS
Starting from version 2.0.2, you may specify a route in terms of an alias. If
this is the case, the alias will rst be converted into the actual route which
will then be turned into an absolute route according to the above rules.
Below are some examples of using this method:
// /index.php?r=site/index
echo Url::toRoute('site/index');
// /index.php?r=site/index&src=ref1#name
echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']);
// /index.php?r=post/edit&id=100
assume the alias "@postEdit" is defined
as "post/edit"
echo Url::toRoute(['@postEdit', 'id' => 100]);
// http://www.example.com/index.php?r=site/index
echo Url::toRoute('site/index', true);
// https://www.example.com/index.php?r=site/index
echo Url::toRoute('site/index', 'https');
There's another method
Url::to()
that is very similar to
toRoute().
The
only dierence is that this method requires a route to be specied as an array
only. If a string is given, it will be treated as a URL.
The rst argument could be:
•
an array:
toRoute() will be called to generate the URL. For example:
Please refer to toRoute()
['site/index'], ['post/index', 'page' => 2].
for more details on how to specify a route.
•
a string with a leading
@:
it is treated as an alias, and the corresponding
aliased string will be returned.
•
•
an empty string: the currently requested URL will be returned;
a normal string: it will be returned as is.
When
$scheme is specied (either a string or true),
info (obtained from
an absolute URL with host
yii\web\UrlManager::$hostInfo)
will be returned. If
$url is already an absolute URL, its scheme will be replaced with the specied
one.
Below are some usage examples:
// /index.php?r=site/index
echo Url::to(['site/index']);
// /index.php?r=site/index&src=ref1#name
echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']);
// /index.php?r=post/edit&id=100
assume the alias "@postEdit" is defined
as "post/edit"
echo Url::to(['@postEdit', 'id' => 100]);
// the currently requested URL
echo Url::to();
16.4.
URL HELPER
489
// /images/logo.gif
echo Url::to('@web/images/logo.gif');
// images/logo.gif
echo Url::to('images/logo.gif');
// http://www.example.com/images/logo.gif
echo Url::to('@web/images/logo.gif', true);
// https://www.example.com/images/logo.gif
echo Url::to('@web/images/logo.gif', 'https');
Starting from version 2.0.3, you may use
yii\helpers\Url::current()
to
create a URL based on the currently requested route and GET parameters.
You may modify or remove some of the GET parameters or add new ones
by passing a
$params
parameter to the method. For example,
// assume $_GET = ['id' => 123, 'src' => 'google'], current route is "post/
view"
// /index.php?r=post/view&id=123&src=google
echo Url::current();
// /index.php?r=post/view&id=123
echo Url::current(['src' => null]);
// /index.php?r=post/view&id=100&src=google
echo Url::current(['id' => 100]);
16.4.3 Remember URLs
There are cases when you need to remember URL and afterwards use it
during processing of the one of sequential requests. It can be achieved in the
following way:
// Remember current URL
Url::remember();
// Remember URL specified. See Url::to() for argument format.
Url::remember(['product/view', 'id' => 42]);
// Remember URL specified with a name given
Url::remember(['product/view', 'id' => 42], 'product');
In the next request we can get URL remembered in the following way:
$url = Url::previous();
$productUrl = Url::previous('product');
16.4.4 Checking Relative URLs
To nd out if URL is relative i.e. it doesn't have host info part, you can use
the following code:
490
$isRelative = Url::isRelative('test/it');
CHAPTER 16.
HELPERS