Commit 16724d2f by Thiago Talma

Merge branch 'master' of https://github.com/yiisoft/yii2

parents df66c41c dc4feef7
<?php <?php
return [ return [
'preload' => [
'debug',
],
'modules' => [
'debug' => 'yii\debug\Module',
'gii' => 'yii\gii\Module',
],
]; ];
<?php <?php
return [ return [
'preload' => [
//'debug',
],
'modules' => [
// 'debug' => 'yii\debug\Module',
// 'gii' => 'yii\gii\Module',
],
'components' => [ 'components' => [
'db' => [ 'db' => [
'class' => 'yii\db\Connection', 'class' => 'yii\db\Connection',
......
<?php <?php
return [ return [
'preload' => [
'debug',
],
'modules' => [
'debug' => 'yii\debug\Module',
'gii' => 'yii\gii\Module',
],
]; ];
...@@ -271,6 +271,12 @@ whose subtotal is greater than 100. To specify a different threshold value, use ...@@ -271,6 +271,12 @@ whose subtotal is greater than 100. To specify a different threshold value, use
$orders = $customer->getBigOrders(200)->all(); $orders = $customer->getBigOrders(200)->all();
``` ```
> Note: A relation method returns an instance of [[yii\db\ActiveRelation]]. If you access the relation like
an attribute, the return value will be the query result of the relation, which could be an instance of `ActiveRecord`,
an array of that, or null, depending the multiplicity of the relation. For example, `$customer->getOrders()` returns
an `ActiveRelation` instance, while `$customer->orders` returns an array of `Order` objects (or an empty array if
the query results in nothing).
Relations with Pivot Table Relations with Pivot Table
-------------------------- --------------------------
...@@ -543,16 +549,19 @@ Finally when calling [[yii\db\ActiveRecord::delete()|delete()]] to delete an Act ...@@ -543,16 +549,19 @@ Finally when calling [[yii\db\ActiveRecord::delete()|delete()]] to delete an Act
3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: will trigger an [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] event 3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: will trigger an [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] event
Custom scopes Scopes
------------- ------
When [[yii\db\ActiveRecord::find()|find()]] or [[yii\db\ActiveRecord::findBySql()|findBySql()]], it returns an [[yii\db\ActiveRecord::yii\db\ActiveQuery|yii\db\ActiveQuery]]
instance. You may call additional query methods, such as `where()`, `orderBy()`, to further specify the query conditions, etc.
When [[yii\db\ActiveRecord::find()|find()]] or [[yii\db\ActiveRecord::findBySql()|findBySql()]] Active Record method is being called without parameters it returns an [[yii\db\ActiveRecord::yii\db\ActiveQuery|yii\db\ActiveQuery]] It is possible that you may want to call the same set of query methods in different places. If this is the case,
instance. This object holds all the parameters and conditions for a future query and also allows you to customize these you should consider defining the so-called *scopes*. A scope is essentially a method defined in a custom query class that
using a set of methods that are called scopes. By default there is a good set of such methods some of which we've calls a set of query methods to modify the query object. You can then use a scope like calling a normal query method.
already used above: `where`, `orderBy`, `limit` etc.
In many cases it is convenient to wrap extra conditions into custom scope methods. In order to do so you need two things. Two steps are required to define a scope. First create a custom query class for your model and define the needed scope
First is creating a custom query class for your model. For example, a `Comment` may have a `CommentQuery`: methods in this class. For example, create a `CommentQuery` class for the `Comment` model and define the `active()`
scope method like the following:
```php ```php
namespace app\models; namespace app\models;
...@@ -575,7 +584,8 @@ Important points are: ...@@ -575,7 +584,8 @@ Important points are:
2. A method should be `public` and should return `$this` in order to allow method chaining. It may accept parameters. 2. A method should be `public` and should return `$this` in order to allow method chaining. It may accept parameters.
3. Check `ActiveQuery` methods that are very useful for modifying query conditions. 3. Check `ActiveQuery` methods that are very useful for modifying query conditions.
The second step is to use `CommentQuery` instead of regular `ActiveQuery` for `Comment` model: Second, override `ActiveRecord::createQuery()` to use the custom query class instead of the regular `ActiveQuery`.
For the example above, you need to write the following code:
``` ```
namespace app\models; namespace app\models;
...@@ -621,6 +631,21 @@ $posts = Post::find()->with([ ...@@ -621,6 +631,21 @@ $posts = Post::find()->with([
])->all(); ])->all();
``` ```
### Default Scope
If you used Yii 1.1 before, you may know a concept called *default scope*. A default scope is a scope that
applies to ALL queries. You can define a default scope easily by overriding `ActiveRecord::createQuery()`. For example,
```php
public static function createQuery()
{
$query = new CommentQuery(['modelClass' => get_called_class()]);
$query->where(['deleted' => false]);
return $query;
}
```
### Making it IDE-friendly ### Making it IDE-friendly
In order to make most modern IDE autocomplete happy you need to override return types for some methods of both model In order to make most modern IDE autocomplete happy you need to override return types for some methods of both model
......
...@@ -91,7 +91,7 @@ If controller is located inside a module its action internal route will be `modu ...@@ -91,7 +91,7 @@ If controller is located inside a module its action internal route will be `modu
In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404. In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404.
> Note: If controller name or action name contains camelCased words, internal route will use dashes i.e. for > Note: If module name, controller name or action name contains camelCased words, internal route will use dashes i.e. for
`DateTimeController::actionFastForward` route will be `date-time/fast-forward`. `DateTimeController::actionFastForward` route will be `date-time/fast-forward`.
### Defaults ### Defaults
......
...@@ -78,7 +78,7 @@ Yii tries to load appropriate translation from one of the message sources define ...@@ -78,7 +78,7 @@ Yii tries to load appropriate translation from one of the message sources define
``` ```
In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're
handling everything that begins with `app`. You can also specify default translation, for more info see [this](specifying-default-translation) example. handling everything that begins with `app`. You can also specify default translation, for more info see [this example](i18n.md#examples).
`class` defines which message source is used. The following message sources are available: `class` defines which message source is used. The following message sources are available:
...@@ -356,7 +356,8 @@ class Module extends \yii\base\Module ...@@ -356,7 +356,8 @@ class Module extends \yii\base\Module
} }
``` ```
In the example above we are using wildcard for matching and then filtering each category per needed file. In the example above we are using wildcard for matching and then filtering each category per needed file. Instead of using `fileMap` you can simply
use convention of category mapping to the same named file and use `Module::t('validation', 'your custom validation message')` or `Module::t('form', 'some form label')` directly.
###Translating widgets messages ###Translating widgets messages
...@@ -405,6 +406,8 @@ class Menu extends Widget ...@@ -405,6 +406,8 @@ class Menu extends Widget
} }
``` ```
Instead of using `fileMap` you can simply use convention of category mapping to the same named file and use `Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])` directly.
> **Note**: For widgets you also can use i18n views, same rules as for controllers are applied to them too. > **Note**: For widgets you also can use i18n views, same rules as for controllers are applied to them too.
TBD: provided classes overview. TBD: provided classes overview.
Model Model
===== =====
In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data. Yii models have the following basic features: In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data.
Yii models have the following basic features:
- Attribute declaration: a model defines what is considered an attribute. - Attribute declaration: a model defines what is considered an attribute.
- Attribute labels: each attribute may be associated with a label for display purpose. - Attribute labels: each attribute may be associated with a label for display purpose.
- Massive attribute assignment: the ability to populate multiple model attributes in one step. - Massive attribute assignment: the ability to populate multiple model attributes in one step.
- Scenario-based data validation. - Scenario-based data validation.
Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data (aka, the business logic). The business logic greatly simplifies the generation of models from complex web forms by providing validation and error reporting. Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define
The Model class is also the base class for more advanced models with additional functionality, such as [Active Record](active-record.md). the validation rules for that data (aka, the business logic). The business logic greatly simplifies the generation
of models from complex web forms by providing validation and error reporting.
The Model class is also the base class for more advanced models with additional functionality, such
as [Active Record](active-record.md).
Attributes Attributes
---------- ----------
...@@ -21,7 +26,7 @@ may contain a `title` attribute and a `content` attribute, accessible as follows ...@@ -21,7 +26,7 @@ may contain a `title` attribute and a `content` attribute, accessible as follows
```php ```php
$post = new Post; $post = new Post;
$post->title = 'Hello, world'; $post->title = 'Hello, world';
$post->content = 'Something interesting is happening'; $post->content = 'Something interesting is happening.';
echo $post->title; echo $post->title;
echo $post->content; echo $post->content;
``` ```
...@@ -62,8 +67,11 @@ Attribute labels are mainly used for display purpose. For example, given an attr ...@@ -62,8 +67,11 @@ Attribute labels are mainly used for display purpose. For example, given an attr
a label `First Name` that is more user-friendly when displayed to end users in places such as form labels and a label `First Name` that is more user-friendly when displayed to end users in places such as form labels and
error messages. Given an attribute name, you can obtain its label by calling [[\yii\base\Model::getAttributeLabel()]]. error messages. Given an attribute name, you can obtain its label by calling [[\yii\base\Model::getAttributeLabel()]].
To declare attribute labels, override the [[\yii\base\Model::attributeLabels()]] method. The overridden method returns a mapping of attribute names to attribute labels, as shown in the example below. If an attribute is not found To declare attribute labels, override the [[\yii\base\Model::attributeLabels()]] method. The overridden method returns
in this mapping, its label will be generated using the [[\yii\base\Model::generateAttributeLabel()]] method. In many cases, [[\yii\base\Model::generateAttributeLabel()]] will generate reasonable labels (e.g. `username` to `Username`, `orderNumber` to `Order Number`). a mapping of attribute names to attribute labels, as shown in the example below. If an attribute is not found
in this mapping, its label will be generated using the [[\yii\base\Model::generateAttributeLabel()]] method.
In many cases, [[\yii\base\Model::generateAttributeLabel()]] will generate reasonable labels (e.g. `username` to `Username`,
`orderNumber` to `Order Number`).
```php ```php
// LoginForm has two attributes: username and password // LoginForm has two attributes: username and password
...@@ -86,7 +94,8 @@ Scenarios ...@@ -86,7 +94,8 @@ Scenarios
--------- ---------
A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs, A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs,
but it may also be used for user registration purposes. In the one scenario, every piece of data is required; in the other, only the username and password would be. but it may also be used for user registration purposes. In the one scenario, every piece of data is required;
in the other, only the username and password would be.
To easily implement the business logic for different scenarios, each model has a property named `scenario` To easily implement the business logic for different scenarios, each model has a property named `scenario`
that stores the name of the scenario that the model is currently being used in. As will be explained in the next that stores the name of the scenario that the model is currently being used in. As will be explained in the next
...@@ -112,6 +121,9 @@ class User extends \yii\db\ActiveRecord ...@@ -112,6 +121,9 @@ class User extends \yii\db\ActiveRecord
} }
``` ```
If `scenarios` method is not defined, default scenario is applied. That means attributes with validation rules are
considered *active*.
If you want to keep the default scenario available besides your own scenarios, use inheritance to include it: If you want to keep the default scenario available besides your own scenarios, use inheritance to include it:
```php ```php
class User extends \yii\db\ActiveRecord class User extends \yii\db\ActiveRecord
...@@ -161,9 +173,9 @@ class EmployeeController extends \yii\web\Controller ...@@ -161,9 +173,9 @@ class EmployeeController extends \yii\web\Controller
``` ```
The example above presumes that the model is based upon [Active Record](active-record.md). For basic form models, The example above presumes that the model is based upon [Active Record](active-record.md). For basic form models,
scenarios are rarely needed, as the basic form model is normally tied directly to a single form. scenarios are rarely needed, as the basic form model is normally tied directly to a single form and, as noted above,
The default implementation of the `scenarios()`-method will return all scenarios found in the `rules()` the default implementation of the `scenarios()` returns every property with active validation rule making it always
declaration (explained in the next section) so in simple cases you do not need to define scenarios. available for mass assignment and validation.
Validation Validation
...@@ -211,16 +223,6 @@ When `validate()` is called, the actual validation rules executed are determined ...@@ -211,16 +223,6 @@ When `validate()` is called, the actual validation rules executed are determined
- the rule must be active for the current scenario. - the rule must be active for the current scenario.
### Active Attributes
An attribute is *active* if it is subject to some validations in the current scenario.
### Safe Attributes
An attribute is *safe* if it can be massively assigned in the current scenario.
Massive Attribute Retrieval and Assignment Massive Attribute Retrieval and Assignment
------------------------------------------ ------------------------------------------
...@@ -229,18 +231,23 @@ The following code will return *all* attributes in the `$post` model ...@@ -229,18 +231,23 @@ The following code will return *all* attributes in the `$post` model
as an array of name-value pairs. as an array of name-value pairs.
```php ```php
$attributes = $post->attributes; $post = Post::find(42);
var_dump($attributes); if ($post) {
$attributes = $post->attributes;
var_dump($attributes);
}
``` ```
Using the same `attributes` property you can massively assign data from associative array to model attributes: Using the same `attributes` property you can massively assign data from associative array to model attributes:
```php ```php
$post = new Post();
$attributes = [ $attributes = [
'title' => 'Model attributes', 'title' => 'Massive assignment example',
'created_at' => time(), 'content' => 'Never allow assigning attributes that are not meant to be assigned.',
]; ];
$post->attributes = $attributes; $post->attributes = $attributes;
var_dump($attributes);
``` ```
In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass
...@@ -256,31 +263,151 @@ rules are described in `rules()` method of the model while what's safe for mass ...@@ -256,31 +263,151 @@ rules are described in `rules()` method of the model while what's safe for mass
assignment is described in `scenarios` method: assignment is described in `scenarios` method:
```php ```php
function rules() class User extends ActiveRecord
{
public function rules()
{
return [
// rule applied when corresponding field is "safe"
['username', 'string', 'length' => [4, 32]],
['first_name', 'string', 'max' => 128],
['password', 'required'],
// rule applied when scenario is "signup" no matter if field is "safe" or not
['hashcode', 'check', 'on' => 'signup'],
];
}
public function scenarios()
{
return [
// on signup allow mass assignment of username
'signup' => ['username', 'password'],
'update' => ['username', 'first_name'],
];
}
}
```
For the code above mass assignment will be allowed stsrictly according to `scenarios()`:
```php
$user = User::find(42);
$data = ['password' => '123'];
$user->attributes = $data;
print_r($data);
```
Will give you empty array because there's no default scenario defined in our `scenarios()`.
```php
$user = User::find(42);
$user->scenario = 'signup';
$data = [
'username' => 'samdark',
'password' => '123',
'hashcode' => 'test',
];
$user->attributes = $data;
print_r($data);
```
Will give you the following:
```php
array(
'username' => 'samdark',
'first_name' => null,
'password' => '123',
'hashcode' => null, // it's not defined in scenarios method
)
```
In case of not defined `scenarios` method like the following:
```php
class User extends ActiveRecord
{ {
return [ public function rules()
// rule applied when corresponding field is "safe" {
['username', 'string', 'length' => [4, 32]], return [
['first_name', 'string', 'max' => 128], ['username', 'string', 'length' => [4, 32]],
['password', 'required'], ['first_name', 'string', 'max' => 128],
['password', 'required'],
// rule applied when scenario is "signup" no matter if field is "safe" or not ];
['hashcode', 'check', 'on' => 'signup'], }
];
} }
```
The code above assumes default scenario so mass assignment will be available for all fields with `rules` defined:
```php
$user = User::find(42);
$data = [
'username' => 'samdark',
'first_name' => 'Alexander',
'last_name' => 'Makarov',
'password' => '123',
];
$user->attributes = $data;
print_r($data);
```
Will give you the following:
```php
array(
'username' => 'samdark',
'first_name' => 'Alexander',
'password' => '123',
)
```
If you want some fields to be unsafe for default scenario:
function scenarios() ```php
class User extends ActiveRecord
{ {
return [ function rules()
// on signup allow mass assignment of username {
'signup' => ['username', 'password'], return [
'update' => ['username', 'first_name'], ['username', 'string', 'length' => [4, 32]],
]; ['first_name', 'string', 'max' => 128],
['password', 'required'],
];
}
public function scenarios()
{
return [
self::SCENARIO_DEFAULT => ['username', 'first_name', '!password']
];
}
} }
``` ```
Note that everything is unsafe by default and you can't make field "safe" without specifying scenario. Mass assignment is still available by default:
```php
$user = User::find(42);
$data = [
'username' => 'samdark',
'first_name' => 'Alexander',
'password' => '123',
];
$user->attributes = $data;
print_r($user->attributes);
```
The code above gives you:
```php
array(
'username' => 'samdark',
'first_name' => 'Alexander',
'password' => null, // because of ! before field name in scenarios
)
```
See also See also
-------- --------
......
...@@ -232,6 +232,7 @@ abstract class Renderer extends BaseRenderer implements ViewContextInterface ...@@ -232,6 +232,7 @@ abstract class Renderer extends BaseRenderer implements ViewContextInterface
*/ */
public function renderInheritance($class) public function renderInheritance($class)
{ {
$parents = [];
$parents[] = $this->typeLink($class); $parents[] = $this->typeLink($class);
while ($class->parentClass !== null) { while ($class->parentClass !== null) {
if(isset($this->context->classes[$class->parentClass])) { if(isset($this->context->classes[$class->parentClass])) {
......
...@@ -9,6 +9,8 @@ namespace yii\debug; ...@@ -9,6 +9,8 @@ namespace yii\debug;
use yii\web\AssetBundle; use yii\web\AssetBundle;
/** /**
* Debugger asset bundle
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
......
...@@ -72,6 +72,13 @@ class LogTarget extends Target ...@@ -72,6 +72,13 @@ class LogTarget extends Target
$this->updateIndexFile($indexFile, $summary); $this->updateIndexFile($indexFile, $summary);
} }
/**
* Updates index file with summary log data
*
* @param string $indexFile path to index file
* @param array $summary summary log data
* @throws \yii\base\InvalidConfigException
*/
private function updateIndexFile($indexFile, $summary) private function updateIndexFile($indexFile, $summary)
{ {
touch($indexFile); touch($indexFile);
......
...@@ -50,7 +50,9 @@ class Module extends \yii\base\Module ...@@ -50,7 +50,9 @@ class Module extends \yii\base\Module
*/ */
public $historySize = 50; public $historySize = 50;
/**
* @inheritdoc
*/
public function init() public function init()
{ {
parent::init(); parent::init();
...@@ -69,13 +71,16 @@ class Module extends \yii\base\Module ...@@ -69,13 +71,16 @@ class Module extends \yii\base\Module
} }
} }
/**
* @inheritdoc
*/
public function beforeAction($action) public function beforeAction($action)
{ {
Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']); Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']);
unset(Yii::$app->getLog()->targets['debug']); unset(Yii::$app->getLog()->targets['debug']);
$this->logTarget = null; $this->logTarget = null;
if ($this->checkAccess($action)) { if ($this->checkAccess()) {
return parent::beforeAction($action); return parent::beforeAction($action);
} elseif ($action->id === 'toolbar') { } elseif ($action->id === 'toolbar') {
return false; return false;
...@@ -84,6 +89,11 @@ class Module extends \yii\base\Module ...@@ -84,6 +89,11 @@ class Module extends \yii\base\Module
} }
} }
/**
* Renders mini-toolbar at the end of page body.
*
* @param \yii\base\Event $event
*/
public function renderToolbar($event) public function renderToolbar($event)
{ {
if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) { if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
...@@ -99,6 +109,10 @@ class Module extends \yii\base\Module ...@@ -99,6 +109,10 @@ class Module extends \yii\base\Module
echo '<script>' . $view->renderPhpFile(__DIR__ . '/assets/toolbar.js') . '</script>'; echo '<script>' . $view->renderPhpFile(__DIR__ . '/assets/toolbar.js') . '</script>';
} }
/**
* Checks if current user is allowed to access the module
* @return boolean if access is granted
*/
protected function checkAccess() protected function checkAccess()
{ {
$ip = Yii::$app->getRequest()->getUserIP(); $ip = Yii::$app->getRequest()->getUserIP();
...@@ -111,6 +125,9 @@ class Module extends \yii\base\Module ...@@ -111,6 +125,9 @@ class Module extends \yii\base\Module
return false; return false;
} }
/**
* @return array default set of panels
*/
protected function corePanels() protected function corePanels()
{ {
return [ return [
......
...@@ -73,6 +73,11 @@ class Panel extends Component ...@@ -73,6 +73,11 @@ class Panel extends Component
return null; return null;
} }
/**
* Loads data into the panel
*
* @param mixed $data
*/
public function load($data) public function load($data)
{ {
$this->data = $data; $this->data = $data;
......
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search; namespace yii\debug\components\search;
use yii\base\Component; use yii\base\Component;
use yii\debug\components\search\matchers\MatcherInterface;
/**
* Provides array filtering capabilities.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Filter extends Component class Filter extends Component
{ {
/** /**
* @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]] * @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
*/ */
protected $rules = []; protected $rules = [];
/** /**
* Adds rules for filtering data. Match can be partial or exactly. * Adds data filtering rule.
*
* @param string $name attribute name * @param string $name attribute name
* @param \yii\debug\components\search\matches\Base $rule * @param MatcherInterface $rule
*/ */
public function addMatch($name, $rule) public function addMatcher($name, MatcherInterface $rule)
{ {
if (empty($rule->value) && $rule->value !== 0) { if ($rule->hasValue()) {
return; $this->rules[$name][] = $rule;
} }
$this->rules[$name][] = $rule;
} }
/** /**
* Applies filter on given array and returns filtered data. * Applies filter on a given array and returns filtered data.
*
* @param array $data data to filter * @param array $data data to filter
* @return array filtered data * @return array filtered data
*/ */
...@@ -36,7 +47,7 @@ class Filter extends Component ...@@ -36,7 +47,7 @@ class Filter extends Component
$filtered = []; $filtered = [];
foreach ($data as $row) { foreach ($data as $row) {
if ($this->checkFilter($row)) { if ($this->passesFilter($row)) {
$filtered[] = $row; $filtered[] = $row;
} }
} }
...@@ -45,28 +56,25 @@ class Filter extends Component ...@@ -45,28 +56,25 @@ class Filter extends Component
} }
/** /**
* Check if the given data satisfies filters. * Checks if the given data satisfies filters.
* @param array $row *
* @param array $row data
* @return boolean if data passed filtering
*/ */
public function checkFilter(array $row) private function passesFilter(array $row)
{ {
$matched = true;
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($this->rules[$name])) { if (isset($this->rules[$name])) {
// check all rules for a given attribute
#check all rules for given attribute
foreach ($this->rules[$name] as $rule) { foreach ($this->rules[$name] as $rule) {
if (!$rule->check($value)) { /** @var MatcherInterface $rule */
$matched = false; if (!$rule->match($value)) {
return false;
} }
} }
} }
} }
return $matched; return true;
} }
} }
...@@ -5,22 +5,36 @@ ...@@ -5,22 +5,36 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\debug\components\search\matches; namespace yii\debug\components\search\matchers;
use yii\base\Component; use yii\base\Component;
/** /**
* Base mathcer class for all matchers that will be used with filter. * Base class for matchers that are used in a filter.
* *
* @author Mark Jebri <mark.github@yandex.ru> * @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0 * @since 2.0
*/ */
abstract class Base extends Component implements MatcherInterface abstract class Base extends Component implements MatcherInterface
{ {
/**
* @var mixed base value to check
*/
protected $baseValue;
/** /**
* @var mixed current value to check for the matcher * @inheritdoc
*/ */
public $value; public function setValue($value)
{
$this->baseValue = $value;
}
/**
* @inheritdoc
*/
public function hasValue()
{
return !empty($this->baseValue) || $this->baseValue === 0;
}
} }
...@@ -5,23 +5,21 @@ ...@@ -5,23 +5,21 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\debug\components\search\matches; namespace yii\debug\components\search\matchers;
/** /**
* Checks if the given value is greater than the base one.
* *
* @author Mark Jebri <mark.github@yandex.ru> * @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0 * @since 2.0
*/ */
class Greater extends Base class GreaterThan extends Base
{ {
/** /**
* Checks if the given value is the same as base one or has partial match with base one. * @inheritdoc
* @param mixed $value
*/ */
public function check($value) public function match($value)
{ {
return ($value > $this->value); return ($value > $this->baseValue);
} }
} }
...@@ -5,23 +5,21 @@ ...@@ -5,23 +5,21 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\debug\components\search\matches; namespace yii\debug\components\search\matchers;
/** /**
* Checks if the given value is lower than the base one.
* *
* @author Mark Jebri <mark.github@yandex.ru> * @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0 * @since 2.0
*/ */
class Lower extends Base class LowerThan extends Base
{ {
/** /**
* Checks if the given value is the same as base one or has partial match with base one. * @inheritdoc
* @param mixed $value
*/ */
public function check($value) public function match($value)
{ {
return ($value < $this->value); return ($value < $this->baseValue);
} }
} }
...@@ -5,21 +5,35 @@ ...@@ -5,21 +5,35 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\debug\components\search\matches; namespace yii\debug\components\search\matchers;
/** /**
* MatcherInterface is the interface that should be implemented by all matchers that will be used in filter. * MatcherInterface should be implemented by all matchers that are used in a filter.
* *
* @author Mark Jebri <mark.github@yandex.ru> * @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0 * @since 2.0
*/ */
interface MatcherInterface interface MatcherInterface
{ {
/**
* Checks if the value passed matches base value.
*
* @param mixed $value value to be matched
* @return boolean if there is a match
*/
public function match($value);
/** /**
* Check if the value is correct according current matcher. * Sets base value to match against
*
* @param mixed $value * @param mixed $value
*/ */
public function check($value); public function setValue($value);
/**
* Checks if base value is set
*
* @return boolean if base value is set
*/
public function hasValue();
} }
...@@ -5,32 +5,30 @@ ...@@ -5,32 +5,30 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\debug\components\search\matches; namespace yii\debug\components\search\matchers;
/** /**
* Checks if the given value is exactly or partially same as the base one.
* *
* @author Mark Jebri <mark.github@yandex.ru> * @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0 * @since 2.0
*/ */
class Exact extends Base class SameAs extends Base
{ {
/** /**
* @var boolean if current matcher should consider partial match of given value. * @var boolean if partial match should be used.
*/ */
public $partial = false; public $partial = false;
/** /**
* Checks if the given value is the same as base one or has partial match with base one. * @inheritdoc
* @param mixed $value
*/ */
public function check($value) public function match($value)
{ {
if (!$this->partial) { if (!$this->partial) {
return (mb_strtolower($this->value, 'utf8') == mb_strtolower($value, 'utf8')); return (mb_strtolower($this->baseValue, 'utf8') == mb_strtolower($value, 'utf8'));
} else { } else {
return (mb_strpos(mb_strtolower($value, 'utf8'), mb_strtolower($this->value,'utf8')) !== false); return (mb_strpos(mb_strtolower($value, 'utf8'), mb_strtolower($this->baseValue, 'utf8')) !== false);
} }
} }
} }
...@@ -13,11 +13,16 @@ use yii\web\NotFoundHttpException; ...@@ -13,11 +13,16 @@ use yii\web\NotFoundHttpException;
use yii\debug\models\search\Debug; use yii\debug\models\search\Debug;
/** /**
* Debugger controller
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class DefaultController extends Controller class DefaultController extends Controller
{ {
/**
* @inheritdoc
*/
public $layout = 'main'; public $layout = 'main';
/** /**
* @var \yii\debug\Module * @var \yii\debug\Module
...@@ -28,6 +33,9 @@ class DefaultController extends Controller ...@@ -28,6 +33,9 @@ class DefaultController extends Controller
*/ */
public $summary; public $summary;
/**
* @inheritdoc
*/
public function actions() public function actions()
{ {
$actions = []; $actions = [];
......
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search; namespace yii\debug\models\search;
use yii\base\Model; use yii\base\Model;
use yii\debug\components\search\Filter; use yii\debug\components\search\Filter;
use yii\debug\components\search\matches; use yii\debug\components\search\matchers;
/**
* Base search model
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Base extends Model class Base extends Model
{ {
/** /**
* @param Filter $filter * Adds filtering condition for a given attribute
* @param string $attribute *
* @param boolean $partial * @param Filter $filter filter instance
* @param string $attribute attribute to filter
* @param boolean $partial if partial match should be used
*/ */
public function addCondition($filter, $attribute, $partial = false) public function addCondition(Filter $filter, $attribute, $partial = false)
{ {
$value = $this->$attribute; $value = $this->$attribute;
if (mb_strpos($value, '>') !== false) { if (mb_strpos($value, '>') !== false) {
$value = intval(str_replace('>', '', $value)); $value = intval(str_replace('>', '', $value));
$filter->addMatch($attribute, new matches\Greater(['value' => $value])); $filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value]));
} elseif (mb_strpos($value, '<') !== false) { } elseif (mb_strpos($value, '<') !== false) {
$value = intval(str_replace('<', '', $value)); $value = intval(str_replace('<', '', $value));
$filter->addMatch($attribute, new matches\Lower(['value' => $value])); $filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value]));
} else { } else {
$filter->addMatch($attribute, new matches\Exact(['value' => $value, 'partial' => $partial])); $filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial]));
} }
} }
} }
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search; namespace yii\debug\models\search;
...@@ -6,13 +11,16 @@ use yii\data\ArrayDataProvider; ...@@ -6,13 +11,16 @@ use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter; use yii\debug\components\search\Filter;
/** /**
* Db represents the model behind the search form about current request database queries. * Search model for current request database queries.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/ */
class Db extends Base class Db extends Base
{ {
/** /**
* @var string type attribute input search value * @var string type of the input search value
*/ */
public $type; public $type;
...@@ -21,6 +29,9 @@ class Db extends Base ...@@ -21,6 +29,9 @@ class Db extends Base
*/ */
public $query; public $query;
/**
* @inheritdoc
*/
public function rules() public function rules()
{ {
return [ return [
...@@ -41,8 +52,9 @@ class Db extends Base ...@@ -41,8 +52,9 @@ class Db extends Base
/** /**
* Returns data provider with filled models. Filter applied if needed. * Returns data provider with filled models. Filter applied if needed.
* @param array $params *
* @param array $models * @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider * @return \yii\data\ArrayDataProvider
*/ */
public function search($params, $models) public function search($params, $models)
...@@ -69,5 +81,4 @@ class Db extends Base ...@@ -69,5 +81,4 @@ class Db extends Base
return $dataProvider; return $dataProvider;
} }
} }
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search; namespace yii\debug\models\search;
...@@ -6,7 +11,11 @@ use yii\data\ArrayDataProvider; ...@@ -6,7 +11,11 @@ use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter; use yii\debug\components\search\Filter;
/** /**
* Debug represents the model behind the search form about requests manifest data. * Search model for requests manifest data.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/ */
class Debug extends Base class Debug extends Base
{ {
...@@ -51,6 +60,9 @@ class Debug extends Base ...@@ -51,6 +60,9 @@ class Debug extends Base
*/ */
public $criticalCodes = [400, 404, 500]; public $criticalCodes = [400, 404, 500];
/**
* @inheritdoc
*/
public function rules() public function rules()
{ {
return [ return [
...@@ -76,8 +88,8 @@ class Debug extends Base ...@@ -76,8 +88,8 @@ class Debug extends Base
/** /**
* Returns data provider with filled models. Filter applied if needed. * Returns data provider with filled models. Filter applied if needed.
* @param array $params * @param array $params an array of parameter values indexed by parameter names
* @param array $models * @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider * @return \yii\data\ArrayDataProvider
*/ */
public function search($params, $models) public function search($params, $models)
...@@ -110,13 +122,13 @@ class Debug extends Base ...@@ -110,13 +122,13 @@ class Debug extends Base
} }
/** /**
* Checks if the code is critical: 400 or greater, 500 or greater. * Checks if code is critical.
*
* @param integer $code * @param integer $code
* @return bool * @return boolean
*/ */
public function isCodeCritical($code) public function isCodeCritical($code)
{ {
return in_array($code, $this->criticalCodes); return in_array($code, $this->criticalCodes);
} }
} }
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search; namespace yii\debug\models\search;
...@@ -6,11 +11,14 @@ use yii\data\ArrayDataProvider; ...@@ -6,11 +11,14 @@ use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter; use yii\debug\components\search\Filter;
/** /**
* Log represents the model behind the search form about current request log. * Search model for current request log.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/ */
class Log extends Base class Log extends Base
{ {
/** /**
* @var string ip attribute input search value * @var string ip attribute input search value
*/ */
...@@ -26,6 +34,9 @@ class Log extends Base ...@@ -26,6 +34,9 @@ class Log extends Base
*/ */
public $message; public $message;
/**
* @inheritdoc
*/
public function rules() public function rules()
{ {
return [ return [
...@@ -47,8 +58,9 @@ class Log extends Base ...@@ -47,8 +58,9 @@ class Log extends Base
/** /**
* Returns data provider with filled models. Filter applied if needed. * Returns data provider with filled models. Filter applied if needed.
* @param array $params *
* @param array $models * @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider * @return \yii\data\ArrayDataProvider
*/ */
public function search($params, $models) public function search($params, $models)
...@@ -73,5 +85,4 @@ class Log extends Base ...@@ -73,5 +85,4 @@ class Log extends Base
return $dataProvider; return $dataProvider;
} }
} }
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search; namespace yii\debug\models\search;
...@@ -6,11 +11,14 @@ use yii\data\ArrayDataProvider; ...@@ -6,11 +11,14 @@ use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter; use yii\debug\components\search\Filter;
/** /**
* Profile represents the model behind the search form about current request profiling log. * Search model for current request profiling log.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/ */
class Profile extends Base class Profile extends Base
{ {
/** /**
* @var string method attribute input search value * @var string method attribute input search value
*/ */
...@@ -21,6 +29,9 @@ class Profile extends Base ...@@ -21,6 +29,9 @@ class Profile extends Base
*/ */
public $info; public $info;
/**
* @inheritdoc
*/
public function rules() public function rules()
{ {
return [ return [
...@@ -41,8 +52,9 @@ class Profile extends Base ...@@ -41,8 +52,9 @@ class Profile extends Base
/** /**
* Returns data provider with filled models. Filter applied if needed. * Returns data provider with filled models. Filter applied if needed.
* @param array $params *
* @param array $models * @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider * @return \yii\data\ArrayDataProvider
*/ */
public function search($params, $models) public function search($params, $models)
...@@ -69,5 +81,4 @@ class Profile extends Base ...@@ -69,5 +81,4 @@ class Profile extends Base
return $dataProvider; return $dataProvider;
} }
} }
...@@ -18,26 +18,45 @@ use yii\debug\Panel; ...@@ -18,26 +18,45 @@ use yii\debug\Panel;
*/ */
class ConfigPanel extends Panel class ConfigPanel extends Panel
{ {
/**
* @inheritdoc
*/
public function getName() public function getName()
{ {
return 'Configuration'; return 'Configuration';
} }
/**
* Returns Yii logo ready to use in `<img src="`
*
* @return string base64 representation of the image
*/
public static function getYiiLogo() public static function getYiiLogo()
{ {
return 'data:image/png;base64,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'; return 'data:image/png;base64,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';
} }
/**
* @inheritdoc
*/
public function getSummary() public function getSummary()
{ {
return Yii::$app->view->render('panels/config/summary', ['panel' => $this]); return Yii::$app->view->render('panels/config/summary', ['panel' => $this]);
} }
/**
* @inheritdoc
*/
public function getDetail() public function getDetail()
{ {
return Yii::$app->view->render('panels/config/detail', ['panel' => $this]); return Yii::$app->view->render('panels/config/detail', ['panel' => $this]);
} }
/**
* Returns data about extensions
*
* @return array
*/
public function getExtensions() public function getExtensions()
{ {
$data = []; $data = [];
...@@ -47,6 +66,9 @@ class ConfigPanel extends Panel ...@@ -47,6 +66,9 @@ class ConfigPanel extends Panel
return $data; return $data;
} }
/**
* @inheritdoc
*/
public function save() public function save()
{ {
return [ return [
......
...@@ -20,7 +20,6 @@ use yii\debug\models\search\Db; ...@@ -20,7 +20,6 @@ use yii\debug\models\search\Db;
*/ */
class DbPanel extends Panel class DbPanel extends Panel
{ {
/** /**
* @var array db queries info extracted to array as models, to use with data provider. * @var array db queries info extracted to array as models, to use with data provider.
*/ */
...@@ -31,11 +30,17 @@ class DbPanel extends Panel ...@@ -31,11 +30,17 @@ class DbPanel extends Panel
*/ */
private $_timings; private $_timings;
/**
* @inheritdoc
*/
public function getName() public function getName()
{ {
return 'Database'; return 'Database';
} }
/**
* @inheritdoc
*/
public function getSummary() public function getSummary()
{ {
$timings = $this->calculateTimings(); $timings = $this->calculateTimings();
...@@ -50,6 +55,9 @@ class DbPanel extends Panel ...@@ -50,6 +55,9 @@ class DbPanel extends Panel
]); ]);
} }
/**
* @inheritdoc
*/
public function getDetail() public function getDetail()
{ {
$searchModel = new Db(); $searchModel = new Db();
...@@ -63,7 +71,8 @@ class DbPanel extends Panel ...@@ -63,7 +71,8 @@ class DbPanel extends Panel
} }
/** /**
* Calculates given request profile messages timings. * Calculates given request profile timings.
*
* @return array timings [token, category, timestamp, traces, nesting level, elapsed time] * @return array timings [token, category, timestamp, traces, nesting level, elapsed time]
*/ */
protected function calculateTimings() protected function calculateTimings()
...@@ -74,6 +83,9 @@ class DbPanel extends Panel ...@@ -74,6 +83,9 @@ class DbPanel extends Panel
return $this->_timings; return $this->_timings;
} }
/**
* @inheritdoc
*/
public function save() public function save()
{ {
$target = $this->module->logTarget; $target = $this->module->logTarget;
...@@ -82,7 +94,8 @@ class DbPanel extends Panel ...@@ -82,7 +94,8 @@ class DbPanel extends Panel
} }
/** /**
* Returns total queries time. * Returns total query time.
*
* @param array $timings * @param array $timings
* @return integer total time * @return integer total time
*/ */
...@@ -98,8 +111,8 @@ class DbPanel extends Panel ...@@ -98,8 +111,8 @@ class DbPanel extends Panel
} }
/** /**
* Returns array of models that represents logs of the current request. Can be used with data providers, * Returns an array of models that represents logs of the current request.
* like yii\data\ArrayDataProvider. * Can be used with data providers such as \yii\data\ArrayDataProvider.
* @return array models * @return array models
*/ */
protected function getModels() protected function getModels()
...@@ -110,7 +123,7 @@ class DbPanel extends Panel ...@@ -110,7 +123,7 @@ class DbPanel extends Panel
foreach($timings as $seq => $dbTiming) { foreach($timings as $seq => $dbTiming) {
$this->_models[] = [ $this->_models[] = [
'type' => $this->detectQueryType($dbTiming['info']), 'type' => $this->getQueryType($dbTiming['info']),
'query' => $dbTiming['info'], 'query' => $dbTiming['info'],
'duration' => ($dbTiming['duration'] * 1000), // in milliseconds 'duration' => ($dbTiming['duration'] * 1000), // in milliseconds
'trace' => $dbTiming['trace'], 'trace' => $dbTiming['trace'],
...@@ -123,16 +136,15 @@ class DbPanel extends Panel ...@@ -123,16 +136,15 @@ class DbPanel extends Panel
} }
/** /**
* Detects databse timing type. Detecting is produced through simple parsing to the first space|tab|new row. * Returns databse query type.
* First word before space is timing type. If there is no such words, timing will have empty type. *
* @param string $timing timing procedure string * @param string $timing timing procedure string
* @return string query type select|insert|delete|etc * @return string query type such as select, insert, delete, etc.
*/ */
protected function detectQueryType($timing) protected function getQueryType($timing)
{ {
$timing = ltrim($timing); $timing = ltrim($timing);
preg_match('/^([a-zA-z]*)/', $timing, $matches); preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? $matches[0] : ''; return count($matches) ? $matches[0] : '';
} }
} }
...@@ -20,22 +20,30 @@ use yii\debug\models\search\Log; ...@@ -20,22 +20,30 @@ use yii\debug\models\search\Log;
*/ */
class LogPanel extends Panel class LogPanel extends Panel
{ {
/** /**
* @var array log messages extracted to array as models, to use with data provider. * @var array log messages extracted to array as models, to use with data provider.
*/ */
private $_models; private $_models;
/**
* @inheritdoc
*/
public function getName() public function getName()
{ {
return 'Logs'; return 'Logs';
} }
/**
* @inheritdoc
*/
public function getSummary() public function getSummary()
{ {
return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]); return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]);
} }
/**
* @inheritdoc
*/
public function getDetail() public function getDetail()
{ {
$searchModel = new Log(); $searchModel = new Log();
...@@ -48,6 +56,9 @@ class LogPanel extends Panel ...@@ -48,6 +56,9 @@ class LogPanel extends Panel
]); ]);
} }
/**
* @inheritdoc
*/
public function save() public function save()
{ {
$target = $this->module->logTarget; $target = $this->module->logTarget;
...@@ -56,9 +67,10 @@ class LogPanel extends Panel ...@@ -56,9 +67,10 @@ class LogPanel extends Panel
} }
/** /**
* Returns array of models that represents logs of the current request. Can be used with data providers, * Returns an array of models that represents logs of the current request.
* like yii\data\ArrayDataProvider. * Can be used with data providers, such as \yii\data\ArrayDataProvider.
* @param boolean $refresh if needed to build models from log messages and refresh them. *
* @param boolean $refresh if need to build models from log messages and refresh them.
* @return array models * @return array models
*/ */
protected function getModels($refresh = false) protected function getModels($refresh = false)
...@@ -78,5 +90,4 @@ class LogPanel extends Panel ...@@ -78,5 +90,4 @@ class LogPanel extends Panel
} }
return $this->_models; return $this->_models;
} }
} }
...@@ -25,11 +25,17 @@ class ProfilingPanel extends Panel ...@@ -25,11 +25,17 @@ class ProfilingPanel extends Panel
*/ */
private $_models; private $_models;
/**
* @inheritdoc
*/
public function getName() public function getName()
{ {
return 'Profiling'; return 'Profiling';
} }
/**
* @inheritdoc
*/
public function getSummary() public function getSummary()
{ {
return Yii::$app->view->render('panels/profile/summary', [ return Yii::$app->view->render('panels/profile/summary', [
...@@ -39,6 +45,9 @@ class ProfilingPanel extends Panel ...@@ -39,6 +45,9 @@ class ProfilingPanel extends Panel
]); ]);
} }
/**
* @inheritdoc
*/
public function getDetail() public function getDetail()
{ {
$searchModel = new Profile(); $searchModel = new Profile();
...@@ -53,6 +62,9 @@ class ProfilingPanel extends Panel ...@@ -53,6 +62,9 @@ class ProfilingPanel extends Panel
]); ]);
} }
/**
* @inheritdoc
*/
public function save() public function save()
{ {
$target = $this->module->logTarget; $target = $this->module->logTarget;
...@@ -65,7 +77,7 @@ class ProfilingPanel extends Panel ...@@ -65,7 +77,7 @@ class ProfilingPanel extends Panel
} }
/** /**
* Returns array of profiling models that can be used in data provider. * Returns array of profiling models that can be used in a data provider.
* @return array models * @return array models
*/ */
protected function getModels() protected function getModels()
...@@ -87,5 +99,4 @@ class ProfilingPanel extends Panel ...@@ -87,5 +99,4 @@ class ProfilingPanel extends Panel
} }
return $this->_models; return $this->_models;
} }
} }
...@@ -19,24 +19,37 @@ use yii\debug\Panel; ...@@ -19,24 +19,37 @@ use yii\debug\Panel;
*/ */
class RequestPanel extends Panel class RequestPanel extends Panel
{ {
/**
* @inheritdoc
*/
public function getName() public function getName()
{ {
return 'Request'; return 'Request';
} }
/**
* @inheritdoc
*/
public function getSummary() public function getSummary()
{ {
return Yii::$app->view->render('panels/request/summary', ['panel' => $this]); return Yii::$app->view->render('panels/request/summary', ['panel' => $this]);
} }
/**
* @inheritdoc
*/
public function getDetail() public function getDetail()
{ {
return Yii::$app->view->render('panels/request/detail', ['panel' => $this]); return Yii::$app->view->render('panels/request/detail', ['panel' => $this]);
} }
/**
* @inheritdoc
*/
public function save() public function save()
{ {
$headers = Yii::$app->getRequest()->getHeaders(); $headers = Yii::$app->getRequest()->getHeaders();
$requestHeaders = [];
foreach ($headers as $name => $value) { foreach ($headers as $name => $value) {
if (is_array($value) && count($value) == 1) { if (is_array($value) && count($value) == 1) {
$requestHeaders[$name] = current($value); $requestHeaders[$name] = current($value);
...@@ -95,5 +108,4 @@ class RequestPanel extends Panel ...@@ -95,5 +108,4 @@ class RequestPanel extends Panel
'SESSION' => empty($_SESSION) ? [] : $_SESSION, 'SESSION' => empty($_SESSION) ? [] : $_SESSION,
]; ];
} }
} }
<?php <?php
use yii\helpers\Html; use yii\helpers\Html;
use yii\grid\GridView; use yii\grid\GridView;
use yii\data\ArrayDataProvider;
use yii\log\Logger; use yii\log\Logger;
?> ?>
<h1>Log Messages</h1> <h1>Log Messages</h1>
......
...@@ -33,4 +33,3 @@ echo Tabs::widget([ ...@@ -33,4 +33,3 @@ echo Tabs::widget([
], ],
], ],
]); ]);
?>
...@@ -165,8 +165,7 @@ class Query extends Component implements QueryInterface ...@@ -165,8 +165,7 @@ class Query extends Component implements QueryInterface
*/ */
public function one($db = null) public function one($db = null)
{ {
$options['size'] = 1; $result = $this->createCommand($db)->search(['size' => 1]);
$result = $this->createCommand($db)->search($options);
if (empty($result['hits']['hits'])) { if (empty($result['hits']['hits'])) {
return false; return false;
} }
......
...@@ -123,6 +123,11 @@ class CodeFile extends Object ...@@ -123,6 +123,11 @@ class CodeFile extends Object
} }
} }
/**
* Returns preview or false if it cannot be rendered
*
* @return boolean|string
*/
public function preview() public function preview()
{ {
if (($pos = strrpos($this->path, '.')) !== false) { if (($pos = strrpos($this->path, '.')) !== false) {
...@@ -140,6 +145,11 @@ class CodeFile extends Object ...@@ -140,6 +145,11 @@ class CodeFile extends Object
} }
} }
/**
* Returns diff or false if it cannot be calculated
*
* @return boolean|string
*/
public function diff() public function diff()
{ {
$type = strtolower($this->getType()); $type = strtolower($this->getType());
...@@ -152,6 +162,13 @@ class CodeFile extends Object ...@@ -152,6 +162,13 @@ class CodeFile extends Object
} }
} }
/**
* Renders diff between two sets of lines
*
* @param mixed $lines1
* @param mixed $lines2
* @return string
*/
private function renderDiff($lines1, $lines2) private function renderDiff($lines1, $lines2)
{ {
if (!is_array($lines1)) { if (!is_array($lines1)) {
......
...@@ -190,7 +190,6 @@ abstract class Generator extends Model ...@@ -190,7 +190,6 @@ abstract class Generator extends Model
public function loadStickyAttributes() public function loadStickyAttributes()
{ {
$stickyAttributes = $this->stickyAttributes(); $stickyAttributes = $this->stickyAttributes();
$attributes[] = 'template';
$path = $this->getStickyDataFile(); $path = $this->getStickyDataFile();
if (is_file($path)) { if (is_file($path)) {
$result = json_decode(file_get_contents($path), true); $result = json_decode(file_get_contents($path), true);
......
...@@ -21,6 +21,9 @@ class ActiveField extends \yii\widgets\ActiveField ...@@ -21,6 +21,9 @@ class ActiveField extends \yii\widgets\ActiveField
*/ */
public $model; public $model;
/**
* @inheritdoc
*/
public function init() public function init()
{ {
$stickyAttributes = $this->model->stickyAttributes(); $stickyAttributes = $this->model->stickyAttributes();
......
...@@ -107,6 +107,9 @@ class DefaultController extends Controller ...@@ -107,6 +107,9 @@ class DefaultController extends Controller
} }
} }
/**
* @inheritdoc
*/
public function createUrl($route, $params = []) public function createUrl($route, $params = [])
{ {
if (!isset($params['id']) && $this->generator !== null) { if (!isset($params['id']) && $this->generator !== null) {
...@@ -120,6 +123,13 @@ class DefaultController extends Controller ...@@ -120,6 +123,13 @@ class DefaultController extends Controller
return parent::createUrl($route, $params); return parent::createUrl($route, $params);
} }
/**
* Creates URL for an aciton
*
* @param string $name name of the action
* @param array $params the parameters (name-value pairs) to be included in the generated URL
* @return string the created relative URL
*/
public function createActionUrl($name, $params = []) public function createActionUrl($name, $params = [])
{ {
foreach ($this->module->generators as $id => $generator) { foreach ($this->module->generators as $id => $generator) {
......
...@@ -33,17 +33,26 @@ class Generator extends \yii\gii\Generator ...@@ -33,17 +33,26 @@ class Generator extends \yii\gii\Generator
public $indexWidgetType = 'grid'; public $indexWidgetType = 'grid';
public $searchModelClass; public $searchModelClass;
/**
* @inheritdoc
*/
public function getName() public function getName()
{ {
return 'CRUD Generator'; return 'CRUD Generator';
} }
/**
* @inheritdoc
*/
public function getDescription() public function getDescription()
{ {
return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete)
operations for the specified data model.'; operations for the specified data model.';
} }
/**
* @inheritdoc
*/
public function rules() public function rules()
{ {
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
...@@ -61,6 +70,9 @@ class Generator extends \yii\gii\Generator ...@@ -61,6 +70,9 @@ class Generator extends \yii\gii\Generator
]); ]);
} }
/**
* @inheritdoc
*/
public function attributeLabels() public function attributeLabels()
{ {
return array_merge(parent::attributeLabels(), [ return array_merge(parent::attributeLabels(), [
...@@ -94,6 +106,9 @@ class Generator extends \yii\gii\Generator ...@@ -94,6 +106,9 @@ class Generator extends \yii\gii\Generator
]; ];
} }
/**
* @inheritdoc
*/
public function requiredTemplates() public function requiredTemplates()
{ {
return ['controller.php']; return ['controller.php'];
...@@ -107,6 +122,9 @@ class Generator extends \yii\gii\Generator ...@@ -107,6 +122,9 @@ class Generator extends \yii\gii\Generator
return ['baseControllerClass', 'moduleID', 'indexWidgetType']; return ['baseControllerClass', 'moduleID', 'indexWidgetType'];
} }
/**
* Checks if model class is valid
*/
public function validateModelClass() public function validateModelClass()
{ {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
...@@ -117,6 +135,9 @@ class Generator extends \yii\gii\Generator ...@@ -117,6 +135,9 @@ class Generator extends \yii\gii\Generator
} }
} }
/**
* Checks if model ID is valid
*/
public function validateModuleID() public function validateModuleID()
{ {
if (!empty($this->moduleID)) { if (!empty($this->moduleID)) {
...@@ -184,6 +205,7 @@ class Generator extends \yii\gii\Generator ...@@ -184,6 +205,7 @@ class Generator extends \yii\gii\Generator
} }
/** /**
* Generates code for active field
* @param string $attribute * @param string $attribute
* @return string * @return string
*/ */
...@@ -217,6 +239,7 @@ class Generator extends \yii\gii\Generator ...@@ -217,6 +239,7 @@ class Generator extends \yii\gii\Generator
} }
/** /**
* Generates code for active search field
* @param string $attribute * @param string $attribute
* @return string * @return string
*/ */
...@@ -235,6 +258,7 @@ class Generator extends \yii\gii\Generator ...@@ -235,6 +258,7 @@ class Generator extends \yii\gii\Generator
} }
/** /**
* Generates column format
* @param \yii\db\ColumnSchema $column * @param \yii\db\ColumnSchema $column
* @return string * @return string
*/ */
...@@ -298,6 +322,9 @@ class Generator extends \yii\gii\Generator ...@@ -298,6 +322,9 @@ class Generator extends \yii\gii\Generator
return $rules; return $rules;
} }
/**
* @return array searchable attributes
*/
public function getSearchAttributes() public function getSearchAttributes()
{ {
return $this->getColumnNames(); return $this->getColumnNames();
...@@ -309,6 +336,7 @@ class Generator extends \yii\gii\Generator ...@@ -309,6 +336,7 @@ class Generator extends \yii\gii\Generator
*/ */
public function generateSearchLabels() public function generateSearchLabels()
{ {
/** @var \yii\base\Model $model */
$model = new $this->modelClass(); $model = new $this->modelClass();
$attributeLabels = $model->attributeLabels(); $attributeLabels = $model->attributeLabels();
$labels = []; $labels = [];
...@@ -330,11 +358,16 @@ class Generator extends \yii\gii\Generator ...@@ -330,11 +358,16 @@ class Generator extends \yii\gii\Generator
return $labels; return $labels;
} }
/**
* Generates search conditions
* @return array
*/
public function generateSearchConditions() public function generateSearchConditions()
{ {
$columns = []; $columns = [];
if (($table = $this->getTableSchema()) === false) { if (($table = $this->getTableSchema()) === false) {
$class = $this->modelClass; $class = $this->modelClass;
/** @var \yii\base\Model $model */
$model = new $class(); $model = new $class();
foreach ($model->attributes() as $attribute) { foreach ($model->attributes() as $attribute) {
$columns[$attribute] = 'unknown'; $columns[$attribute] = 'unknown';
...@@ -369,6 +402,10 @@ class Generator extends \yii\gii\Generator ...@@ -369,6 +402,10 @@ class Generator extends \yii\gii\Generator
return $conditions; return $conditions;
} }
/**
* Generates URL parameters
* @return string
*/
public function generateUrlParams() public function generateUrlParams()
{ {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
...@@ -385,6 +422,10 @@ class Generator extends \yii\gii\Generator ...@@ -385,6 +422,10 @@ class Generator extends \yii\gii\Generator
} }
} }
/**
* Generates action parameters
* @return string
*/
public function generateActionParams() public function generateActionParams()
{ {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
...@@ -397,6 +438,10 @@ class Generator extends \yii\gii\Generator ...@@ -397,6 +438,10 @@ class Generator extends \yii\gii\Generator
} }
} }
/**
* Generates parameter tags for phpdoc
* @return array parameter tags for phpdoc
*/
public function generateActionParamComments() public function generateActionParamComments()
{ {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
...@@ -420,6 +465,10 @@ class Generator extends \yii\gii\Generator ...@@ -420,6 +465,10 @@ class Generator extends \yii\gii\Generator
} }
} }
/**
* Returns table schema for current model class or false if it is not an active record
* @return boolean|\yii\db\TableSchema
*/
public function getTableSchema() public function getTableSchema()
{ {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
...@@ -431,6 +480,9 @@ class Generator extends \yii\gii\Generator ...@@ -431,6 +480,9 @@ class Generator extends \yii\gii\Generator
} }
} }
/**
* @return array model column names
*/
public function getColumnNames() public function getColumnNames()
{ {
/** @var ActiveRecord $class */ /** @var ActiveRecord $class */
...@@ -438,6 +490,7 @@ class Generator extends \yii\gii\Generator ...@@ -438,6 +490,7 @@ class Generator extends \yii\gii\Generator
if (is_subclass_of($class, 'yii\db\ActiveRecord')) { if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
return $class::getTableSchema()->getColumnNames(); return $class::getTableSchema()->getColumnNames();
} else { } else {
/** @var \yii\base\Model $model */
$model = new $class(); $model = new $class();
return $model->attributes(); return $model->attributes();
} }
......
...@@ -155,7 +155,7 @@ class BaseImage ...@@ -155,7 +155,7 @@ class BaseImage
$img = $img->thumbnail($box, $mode); $img = $img->thumbnail($box, $mode);
// create empty image to preserve aspect ratio of thumbnail // create empty image to preserve aspect ratio of thumbnail
$thumb = static::getImagine()->create($box); $thumb = static::getImagine()->create($box, new Color('FFF', 100));
// calculate points // calculate points
$size = $img->getSize(); $size = $img->getSize();
......
...@@ -6,6 +6,7 @@ Yii Framework 2 Change Log ...@@ -6,6 +6,7 @@ Yii Framework 2 Change Log
- Bug #1265: AssetController does not override 'js' and 'css' for compressed bundles (klimov-paul) - Bug #1265: AssetController does not override 'js' and 'css' for compressed bundles (klimov-paul)
- Bug #1326: The `visible` setting for `DetailView` doesn't work as expected (qiangxue) - Bug #1326: The `visible` setting for `DetailView` doesn't work as expected (qiangxue)
- Bug #1412: `FileValidator` and `ImageValidator` still trigger `uploadRequired` error in some case when `skipOnEmpty` is true and no upload is provided (qiangxue)
- Bug #1446: Logging while logs are processed causes infinite loop (qiangxue) - Bug #1446: Logging while logs are processed causes infinite loop (qiangxue)
- Bug #1497: Localized view files are not correctly returned (mintao) - Bug #1497: Localized view files are not correctly returned (mintao)
- Bug #1500: Log messages exported to files are not separated by newlines (omnilight, qiangxue) - Bug #1500: Log messages exported to files are not separated by newlines (omnilight, qiangxue)
...@@ -114,6 +115,7 @@ Yii Framework 2 Change Log ...@@ -114,6 +115,7 @@ Yii Framework 2 Change Log
- Enh: Added `yii\web\View::POS_LOAD` (qiangxue) - Enh: Added `yii\web\View::POS_LOAD` (qiangxue)
- Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue) - Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue)
- Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue) - Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue)
- Enh:#2211: Added typecast database types into php types (dizews)
- Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07) - Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07)
- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue) - Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue)
- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue) - Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
...@@ -139,6 +141,7 @@ Yii Framework 2 Change Log ...@@ -139,6 +141,7 @@ Yii Framework 2 Change Log
- Chg #2173: Removed `StringHelper::diff()`, Moved `phpspec/php-diff` dependency from `yiisoft/yii2` to `yiisoft/yii2-gii` (samdark) - Chg #2173: Removed `StringHelper::diff()`, Moved `phpspec/php-diff` dependency from `yiisoft/yii2` to `yiisoft/yii2-gii` (samdark)
- Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue) - Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue)
- Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue) - Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue)
- Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) - Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue) - Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
......
...@@ -515,7 +515,7 @@ abstract class Application extends Module ...@@ -515,7 +515,7 @@ abstract class Application extends Module
$handler->handle($exception); $handler->handle($exception);
} else { } else {
echo $this->renderException($exception); echo $this->renderException($exception);
if (PHP_SAPI === 'cli') { if (PHP_SAPI === 'cli' && !YII_ENV_TEST) {
exit(1); exit(1);
} }
} }
......
...@@ -87,7 +87,10 @@ class ErrorHandler extends Component ...@@ -87,7 +87,10 @@ class ErrorHandler extends Component
{ {
if (Yii::$app instanceof \yii\console\Application || YII_ENV_TEST) { if (Yii::$app instanceof \yii\console\Application || YII_ENV_TEST) {
echo Yii::$app->renderException($exception); echo Yii::$app->renderException($exception);
exit(1); if (!YII_ENV_TEST) {
exit(1);
}
return;
} }
$useErrorView = !YII_DEBUG || $exception instanceof UserException; $useErrorView = !YII_DEBUG || $exception instanceof UserException;
......
...@@ -45,7 +45,7 @@ use yii\validators\Validator; ...@@ -45,7 +45,7 @@ use yii\validators\Validator;
* property is read-only. * property is read-only.
* @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
* read-only. * read-only.
* @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. * @property string $scenario The scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
* @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model. * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model.
* This property is read-only. * This property is read-only.
* *
...@@ -57,7 +57,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -57,7 +57,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
/** /**
* The name of the default scenario. * The name of the default scenario.
*/ */
const DEFAULT_SCENARIO = 'default'; const SCENARIO_DEFAULT = 'default';
/** /**
* @event ModelEvent an event raised at the beginning of [[validate()]]. You may set * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
...@@ -80,7 +80,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -80,7 +80,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
/** /**
* @var string current scenario * @var string current scenario
*/ */
private $_scenario = self::DEFAULT_SCENARIO; private $_scenario = self::SCENARIO_DEFAULT;
/** /**
* Returns the validation rules for attributes. * Returns the validation rules for attributes.
...@@ -170,7 +170,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -170,7 +170,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
* please prefix the attribute with an exclamation character (e.g. '!rank'). * please prefix the attribute with an exclamation character (e.g. '!rank').
* *
* The default implementation of this method will return all scenarios found in the [[rules()]] * The default implementation of this method will return all scenarios found in the [[rules()]]
* declaration. A special scenario named [[DEFAULT_SCENARIO]] will contain all attributes * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes
* found in the [[rules()]]. Each scenario will be associated with the attributes that * found in the [[rules()]]. Each scenario will be associated with the attributes that
* are being validated by the validation rules that apply to the scenario. * are being validated by the validation rules that apply to the scenario.
* *
...@@ -178,7 +178,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -178,7 +178,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
*/ */
public function scenarios() public function scenarios()
{ {
$scenarios = [self::DEFAULT_SCENARIO => []]; $scenarios = [self::SCENARIO_DEFAULT => []];
foreach ($this->getValidators() as $validator) { foreach ($this->getValidators() as $validator) {
foreach ($validator->on as $scenario) { foreach ($validator->on as $scenario) {
$scenarios[$scenario] = []; $scenarios[$scenario] = [];
...@@ -214,7 +214,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -214,7 +214,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
} }
foreach ($scenarios as $scenario => $attributes) { foreach ($scenarios as $scenario => $attributes) {
if (empty($attributes) && $scenario !== self::DEFAULT_SCENARIO) { if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) {
unset($scenarios[$scenario]); unset($scenarios[$scenario]);
} else { } else {
$scenarios[$scenario] = array_keys($attributes); $scenarios[$scenario] = array_keys($attributes);
...@@ -649,7 +649,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab ...@@ -649,7 +649,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
* Scenario affects how validation is performed and which attributes can * Scenario affects how validation is performed and which attributes can
* be massively assigned. * be massively assigned.
* *
* @return string the scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
*/ */
public function getScenario() public function getScenario()
{ {
......
...@@ -275,6 +275,28 @@ class ActiveRecord extends BaseActiveRecord ...@@ -275,6 +275,28 @@ class ActiveRecord extends BaseActiveRecord
} }
/** /**
* @inheritdoc
*/
public static function create($row)
{
$record = static::instantiate($row);
$attributes = array_flip($record->attributes());
$columns = static::getTableSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$value = $columns[$name]->typecast($value);
}
if (isset($attributes[$name])) {
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
}
}
$record->setOldAttributes($record->getAttributes());
return $record;
}
/**
* Inserts a row into the associated database table using the attribute values of this record. * Inserts a row into the associated database table using the attribute values of this record.
* *
* This method performs the following steps in order: * This method performs the following steps in order:
......
...@@ -409,15 +409,12 @@ abstract class Schema extends Object ...@@ -409,15 +409,12 @@ abstract class Schema extends Object
static $typeMap = [ // abstract type => php type static $typeMap = [ // abstract type => php type
'smallint' => 'integer', 'smallint' => 'integer',
'integer' => 'integer', 'integer' => 'integer',
'bigint' => 'integer',
'boolean' => 'boolean', 'boolean' => 'boolean',
'float' => 'double', 'float' => 'double',
]; ];
if (isset($typeMap[$column->type])) { if (isset($typeMap[$column->type])) {
if ($column->type === 'bigint') { if ($column->type === 'integer') {
return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string'; return $column->unsigned ? 'string' : 'integer';
} elseif ($column->type === 'integer') {
return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer';
} else { } else {
return $typeMap[$column->type]; return $typeMap[$column->type];
} }
......
...@@ -111,9 +111,9 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -111,9 +111,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
$value = (int)$value - 1; $value = (int)$value - 1;
} }
try { try {
// it's possible sqlite_sequence does not exist
$db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute(); $db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute();
} catch (Exception $e) { } catch (Exception $e) {
// it's possible that sqlite_sequence does not exist
} }
} elseif ($table === null) { } elseif ($table === null) {
throw new InvalidParamException("Table not found: $tableName"); throw new InvalidParamException("Table not found: $tableName");
......
...@@ -14,6 +14,18 @@ use yii\helpers\Html; ...@@ -14,6 +14,18 @@ use yii\helpers\Html;
/** /**
* ActionColumn is a column for the [[GridView]] widget that displays buttons for viewing and manipulating the items. * ActionColumn is a column for the [[GridView]] widget that displays buttons for viewing and manipulating the items.
* *
* To add an ActionColumn to the gridview, add it to the [[GridView::columns|columns]] configuration as follows:
*
* ```php
* 'columns' => [
* // ...
* [
* 'class' => 'yii\grid\ActionColumn',
* // you may configure additional properties here
* ],
* ]
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
......
...@@ -13,6 +13,19 @@ use yii\helpers\Html; ...@@ -13,6 +13,19 @@ use yii\helpers\Html;
/** /**
* CheckboxColumn displays a column of checkboxes in a grid view. * CheckboxColumn displays a column of checkboxes in a grid view.
*
* * To add a CheckboxColumn to the [[GridView]], add it to the [[GridView::columns|columns]] configuration as follows:
*
* ```php
* '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 * Users may click on the checkboxes to select rows of the grid. The selected rows may be
* obtained by calling the following JavaScript code: * obtained by calling the following JavaScript code:
* *
......
...@@ -67,9 +67,9 @@ class GridView extends BaseListView ...@@ -67,9 +67,9 @@ class GridView extends BaseListView
* returns an array of the HTML attributes. The anonymous function will be called once for every * returns an array of the HTML attributes. The anonymous function will be called once for every
* data model returned by [[dataProvider]]. It should have the following signature: * data model returned by [[dataProvider]]. It should have the following signature:
* *
* ~~~php * ```php
* function ($model, $key, $index, $grid) * function ($model, $key, $index, $grid)
* ~~~ * ```
* *
* - `$model`: the current data model being rendered * - `$model`: the current data model being rendered
* - `$key`: the key value associated with the current data model * - `$key`: the key value associated with the current data model
...@@ -111,7 +111,7 @@ class GridView extends BaseListView ...@@ -111,7 +111,7 @@ class GridView extends BaseListView
* @var array grid column configuration. Each array element represents the configuration * @var array grid column configuration. Each array element represents the configuration
* for one particular grid column. For example, * for one particular grid column. For example,
* *
* ~~~php * ```php
* [ * [
* ['class' => SerialColumn::className()], * ['class' => SerialColumn::className()],
* [ * [
...@@ -122,7 +122,7 @@ class GridView extends BaseListView ...@@ -122,7 +122,7 @@ class GridView extends BaseListView
* ], * ],
* ['class' => CheckboxColumn::className()], * ['class' => CheckboxColumn::className()],
* ] * ]
* ~~~ * ```
* *
* If a column is of class [[DataColumn]], the "class" element can be omitted. * If a column is of class [[DataColumn]], the "class" element can be omitted.
* *
......
...@@ -10,6 +10,18 @@ namespace yii\grid; ...@@ -10,6 +10,18 @@ namespace yii\grid;
/** /**
* SerialColumn displays a column of row numbers (1-based). * SerialColumn displays a column of row numbers (1-based).
* *
* To add a SerialColumn to the [[GridView]], add it to the [[GridView::columns|columns]] configuration as follows:
*
* ```php
* 'columns' => [
* // ...
* [
* 'class' => 'yii\grid\SerialColumn',
* // you may configure additional properties here
* ],
* ]
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
......
...@@ -422,7 +422,7 @@ class BaseConsole ...@@ -422,7 +422,7 @@ class BaseConsole
$styleA = ArrayHelper::merge($styleA, $style); $styleA = ArrayHelper::merge($styleA, $style);
} }
$styleString[] = []; $styleString = [];
foreach ($styleA as $name => $content) { foreach ($styleA as $name => $content) {
if ($name === 'text-decoration') { if ($name === 'text-decoration') {
$content = implode(' ', $content); $content = implode(' ', $content);
......
...@@ -137,7 +137,7 @@ class MessageFormatter extends Component ...@@ -137,7 +137,7 @@ class MessageFormatter extends Component
} }
// replace named arguments // replace named arguments
if (($tokens = $this->tokenizePattern($pattern)) === false) { if (($tokens = self::tokenizePattern($pattern)) === false) {
$this->_errorCode = -1; $this->_errorCode = -1;
$this->_errorMessage = "Message pattern is invalid."; $this->_errorMessage = "Message pattern is invalid.";
return false; return false;
...@@ -187,7 +187,7 @@ class MessageFormatter extends Component ...@@ -187,7 +187,7 @@ class MessageFormatter extends Component
*/ */
private function replaceNamedArguments($pattern, $givenParams, &$resultingParams, &$map = []) private function replaceNamedArguments($pattern, $givenParams, &$resultingParams, &$map = [])
{ {
if (($tokens = $this->tokenizePattern($pattern)) === false) { if (($tokens = self::tokenizePattern($pattern)) === false) {
return false; return false;
} }
foreach($tokens as $i => $token) { foreach($tokens as $i => $token) {
...@@ -214,7 +214,7 @@ class MessageFormatter extends Component ...@@ -214,7 +214,7 @@ class MessageFormatter extends Component
if (!isset($token[2])) { if (!isset($token[2])) {
return false; return false;
} }
$subtokens = $this->tokenizePattern($token[2]); $subtokens = self::tokenizePattern($token[2]);
$c = count($subtokens); $c = count($subtokens);
for ($k = 0; $k + 1 < $c; $k++) { for ($k = 0; $k + 1 < $c; $k++) {
if (is_array($subtokens[$k]) || !is_array($subtokens[++$k])) { if (is_array($subtokens[$k]) || !is_array($subtokens[++$k])) {
...@@ -239,7 +239,7 @@ class MessageFormatter extends Component ...@@ -239,7 +239,7 @@ class MessageFormatter extends Component
*/ */
protected function fallbackFormat($pattern, $args, $locale) protected function fallbackFormat($pattern, $args, $locale)
{ {
if (($tokens = $this->tokenizePattern($pattern)) === false) { if (($tokens = self::tokenizePattern($pattern)) === false) {
$this->_errorCode = -1; $this->_errorCode = -1;
$this->_errorMessage = "Message pattern is invalid."; $this->_errorMessage = "Message pattern is invalid.";
return false; return false;
...@@ -261,7 +261,7 @@ class MessageFormatter extends Component ...@@ -261,7 +261,7 @@ class MessageFormatter extends Component
* @param string $pattern patter to tokenize * @param string $pattern patter to tokenize
* @return array|bool array of tokens or false on failure * @return array|bool array of tokens or false on failure
*/ */
private function tokenizePattern($pattern) private static function tokenizePattern($pattern)
{ {
$depth = 1; $depth = 1;
if (($start = $pos = mb_strpos($pattern, '{')) === false) { if (($start = $pos = mb_strpos($pattern, '{')) === false) {
...@@ -340,7 +340,7 @@ class MessageFormatter extends Component ...@@ -340,7 +340,7 @@ class MessageFormatter extends Component
if (!isset($token[2])) { if (!isset($token[2])) {
return false; return false;
} }
$select = static::tokenizePattern($token[2]); $select = self::tokenizePattern($token[2]);
$c = count($select); $c = count($select);
$message = false; $message = false;
for ($i = 0; $i + 1 < $c; $i++) { for ($i = 0; $i + 1 < $c; $i++) {
...@@ -368,7 +368,7 @@ class MessageFormatter extends Component ...@@ -368,7 +368,7 @@ class MessageFormatter extends Component
if (!isset($token[2])) { if (!isset($token[2])) {
return false; return false;
} }
$plural = static::tokenizePattern($token[2]); $plural = self::tokenizePattern($token[2]);
$c = count($plural); $c = count($plural);
$message = false; $message = false;
$offset = 0; $offset = 0;
......
...@@ -32,30 +32,30 @@ return array ( ...@@ -32,30 +32,30 @@ return array (
'No' => 'Жоқ', 'No' => 'Жоқ',
'No help for unknown command "{command}".' => 'Анықтама белгісіз команда үшін ақиық "{command}".', 'No help for unknown command "{command}".' => 'Анықтама белгісіз команда үшін ақиық "{command}".',
'No help for unknown sub-command "{command}".' => 'Анықтама белгісіз субкоманда үшін ақиық "{command}".', 'No help for unknown sub-command "{command}".' => 'Анықтама белгісіз субкоманда үшін ақиық "{command}".',
'No results found.' => 'Ештене табылган жок.', 'No results found.' => 'Ештене табылған жок.',
'Only files with these extensions are allowed: {extensions}.' => 'Файлды жуктеу тек қана осы аумақтармен: {extensions}.', 'Only files with these extensions are allowed: {extensions}.' => 'Файлды жүктеу тек қана осы аумақтармен: {extensions}.',
'Only files with these mimeTypes are allowed: {mimeTypes}.' => 'Файлды жуктеу тек қана осы MIME-үлгілермен: {mimeTypes}.', 'Only files with these mimeTypes are allowed: {mimeTypes}.' => 'Файлды жүктеу тек қана осы MIME-үлгілермен: {mimeTypes}.',
'Page not found.' => 'Парақ табылган жок.', 'Page not found.' => 'Парақ табылган жок.',
'Please fix the following errors:' => 'Мына қателерді түзеніз:', 'Please fix the following errors:' => 'Мына қателерді түзеніз:',
'Please upload a file.' => 'Файлды жуктеу.', 'Please upload a file.' => 'Файлды жүктеу.',
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => 'Жазбалар көрсетілген <b>{begin, number}-{end, number}</b> дан <b>{totalCount, number}</b>.', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => 'Жазбалар көрсетілген <b>{begin, number}-{end, number}</b> дан <b>{totalCount, number}</b>.',
'The file "{file}" is not an image.' => 'Файл «{file}» сурет емес.', 'The file "{file}" is not an image.' => 'Файл «{file}» сурет емес.',
'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файлдын «{file}» көлемі өте үлкен. Өлшемі осыдан аспау керек {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» көлемі өте үлкен. Өлшемі осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{байт} few{байтар} many{байтар} other{байтар}}.',
'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файлдын «{file}» көлемі өте кіші. Өлшемі осыдан астам болу керек {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» көлемі өте кіші. Өлшемі осыдан астам болу керек,неғұрлым {limit, number} {limit, plural, one{байт} few{байтар} many{байтар} other{байтар}}.',
'The format of {attribute} is invalid.' => 'Форматын мағынасы дұрыс емес «{attribute}».', 'The format of {attribute} is invalid.' => 'Форматың мағынасы дұрыс емес «{attribute}».',
'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ұзындығы осыдан аспау керек {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ұзындығы осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.',
'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ені осыдан аспау керек {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ені осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.',
'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ұзындығы осыдан астам болу керек {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ұзындығы осыдан астам болу керек,неғұрлым limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.',
'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ені осыдан астам болу керек {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ені осыдан астам болу керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.',
'The verification code is incorrect.' => 'Тексеріс коды қате.', 'The verification code is incorrect.' => 'Тексеріс коды қате.',
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'Барі <b>{count, number}</b> {count, plural, one{жазба} few{жазбалар} many{жазбалардың} other{жазбалар}}.', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'Барі <b>{count, number}</b> {count, plural, one{жазба} few{жазбалар} many{жазбалар} other{жазбалар}}.',
'Unable to verify your data submission.' => 'Берілген мәліметердің тексеру сәті болмады.', 'Unable to verify your data submission.' => 'Берілген мәліметердің тексеру сәті болмады.',
'Unknown command "{command}".' => 'Белгісіз команда "{command}".', 'Unknown command "{command}".' => 'Белгісіз команда "{command}".',
'Unknown option: --{name}' => 'Белгісіз опция: --{name}', 'Unknown option: --{name}' => 'Белгісіз опция: --{name}',
'Update' => 'Редакциялау', 'Update' => 'Редакциялау',
'Yes' => 'Я', 'Yes' => 'Я',
'You are not allowed to perform this action.' => 'Сізге адал әрекет жасауға болмайды', 'You are not allowed to perform this action.' => 'Сізге адал әрекет жасауға болмайды',
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Сіз осыдан жүктеуге астам {limit, number} {limit, plural, one{файла} few{файлов} many{файлов} other{файла}}.', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Сіз осыдан жүктеуге астам {limit, number} {limit, plural, one{файла} few{файлдар} many{файлдар} other{файлдар}}.',
'the input value' => 'кіргізілген мағыналар', 'the input value' => 'кіргізілген мағыналар',
'{attribute} "{value}" has already been taken.' => '{attribute} «{value}» Бұл бос емес.', '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» Бұл бос емес.',
'{attribute} cannot be blank.' => 'Толтыруға қажет «{attribute}».', '{attribute} cannot be blank.' => 'Толтыруға қажет «{attribute}».',
...@@ -75,7 +75,7 @@ return array ( ...@@ -75,7 +75,7 @@ return array (
'{attribute} must be no less than {min}.' => 'Мағына «{attribute}» көп болу керек {min}.', '{attribute} must be no less than {min}.' => 'Мағына «{attribute}» көп болу керек {min}.',
'{attribute} must be repeated exactly.' => 'Мағына «{attribute}» дәлме-дәл қайталану керек.', '{attribute} must be repeated exactly.' => 'Мағына «{attribute}» дәлме-дәл қайталану керек.',
'{attribute} must not be equal to "{compareValue}".' => 'Мағына «{attribute}» тең болмау керек «{compareValue}».', '{attribute} must not be equal to "{compareValue}".' => 'Мағына «{attribute}» тең болмау керек «{compareValue}».',
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» минимум болу керек {min, number} {min, plural, one{рәміз} few{рәміздер} many{рәміздерді} other{рәміздер}}.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» минимум болу керек {min, number} {min, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» өте үлкен болау керек {max, number} {max, plural, one{рәміз} few{рәміздер} many{рәміздерді} other{рәміздер}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» өте үлкен болу керек {max, number} {max, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» болу керек {length, number} {length, plural, one{рәміз} few{рәміздер} many{рәміздерді} other{рәміздер}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Мағынада «{attribute}» болу керек {length, number} {length, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.',
); );
...@@ -225,6 +225,14 @@ class FileValidator extends Validator ...@@ -225,6 +225,14 @@ class FileValidator extends Validator
} }
/** /**
* @inheritdoc
*/
public function isEmpty($value, $trim = false)
{
return !$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE;
}
/**
* Converts php.ini style size to bytes * Converts php.ini style size to bytes
* *
* @param string $sizeStr $sizeStr * @param string $sizeStr $sizeStr
......
...@@ -616,6 +616,8 @@ class ActiveField extends Component ...@@ -616,6 +616,8 @@ class ActiveField extends Component
return []; return [];
} }
$options = [];
$enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation; $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
if ($enableClientValidation) { if ($enableClientValidation) {
$validators = []; $validators = [];
......
...@@ -172,7 +172,17 @@ class FileValidatorTest extends TestCase ...@@ -172,7 +172,17 @@ class FileValidatorTest extends TestCase
$val->validateAttribute($m, 'attr_files_empty'); $val->validateAttribute($m, 'attr_files_empty');
$this->assertTrue($m->hasErrors('attr_files_empty')); $this->assertTrue($m->hasErrors('attr_files_empty'));
$this->assertSame($val->uploadRequired, current($m->getErrors('attr_files_empty'))); $this->assertSame($val->uploadRequired, current($m->getErrors('attr_files_empty')));
// single File with skipOnEmpty=false
$val = new FileValidator(['skipOnEmpty' => false]);
$m = $this->createModelForAttributeTest();
$val->validateAttribute($m, 'attr_files');
$this->assertFalse($m->hasErrors());
$val->validateAttribute($m, 'attr_files_empty');
$this->assertTrue($m->hasErrors('attr_files_empty'));
$this->assertSame($val->uploadRequired, current($m->getErrors('attr_files_empty')));
$m = $this->createModelForAttributeTest(); $m = $this->createModelForAttributeTest();
// too big // too big
$val = new FileValidator(['maxSize' => 128]); $val = new FileValidator(['maxSize' => 128]);
$val->validateAttribute($m, 'attr_files'); $val->validateAttribute($m, 'attr_files');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment