Commit b8f14b06 by Alexander Makarov

Merge branch 'master'

Conflicts: framework/db/BaseActiveRecord.php
parents c2e0b5be c07521f4
<?php
return [
'preload' => [
'debug',
],
'modules' => [
'debug' => 'yii\debug\Module',
'gii' => 'yii\gii\Module',
],
];
<?php
return [
'preload' => [
//'debug',
],
'modules' => [
// 'debug' => 'yii\debug\Module',
// 'gii' => 'yii\gii\Module',
],
'components' => [
'db' => [
'class' => 'yii\db\Connection',
......
<?php
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
$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
--------------------------
......@@ -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
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]]
instance. This object holds all the parameters and conditions for a future query and also allows you to customize these
using a set of methods that are called scopes. By default there is a good set of such methods some of which we've
already used above: `where`, `orderBy`, `limit` etc.
It is possible that you may want to call the same set of query methods in different places. If this is the case,
you should consider defining the so-called *scopes*. A scope is essentially a method defined in a custom query class that
calls a set of query methods to modify the query object. You can then use a scope like calling a normal query method.
In many cases it is convenient to wrap extra conditions into custom scope methods. In order to do so you need two things.
First is creating a custom query class for your model. For example, a `Comment` may have a `CommentQuery`:
Two steps are required to define a scope. First create a custom query class for your model and define the needed scope
methods in this class. For example, create a `CommentQuery` class for the `Comment` model and define the `active()`
scope method like the following:
```php
namespace app\models;
......@@ -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.
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;
......@@ -621,6 +631,21 @@ $posts = Post::find()->with([
])->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
In order to make most modern IDE autocomplete happy you need to override return types for some methods of both model
......
Database Fixtures
Managing Fixtures
=================
// todo: this tutorial may be merged into test-fixture.md
Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing
different cases. With this data using your tests becoming more efficient and useful.
Yii supports database fixtures via the `yii fixture` command line tool. This tool supports:
Yii supports fixtures via the `yii fixture` command line tool. This tool supports:
* Applying new fixtures to database tables;
* Clearing, database tables (with sequences);
* Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
* Unloading fixtures in different ways (usually it is clearing storage);
* Auto-generating fixtures and populating it with random data.
Fixtures format
---------------
Fixtures are just plain php files returning array. These files are usually stored under `@tests/unit/fixtures` path, but it
can be [configured](#configure-command-globally) in other way. Example of fixture file:
Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md) on them.
Lets assume we have fixtures data to load:
```
#users.php file under fixtures path
#users.php file under fixtures data path, by default @tests\unit\fixtures\data
return [
[
......@@ -36,31 +38,35 @@ return [
],
];
```
This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reset.
If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb`
fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md).
Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures).
Fixture classes name should not be plural.
Loading fixtures
----------------
Applying fixtures
-----------------
Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can
change this behavior with config or command options.
To apply fixture to the table, run the following command:
To apply fixture, run the following command:
```
yii fixture/apply <tbl_name>
yii fixture/apply <fixture_name>
```
The required `tbl_name` parameter specifies a database table to which data will be loaded. You can load data to several tables at once.
The required `fixture_name` parameter specifies a fixture name which data will be loaded. You can load several fixtures at once.
Below are correct formats of this command:
```
// apply fixtures to the "users" table of database
yii fixture/apply users
// apply `users` fixture
yii fixture/apply User
// same as above, because default action of "fixture" command is "apply"
yii fixture users
yii fixture User
// apply several fixtures to several tables. Note that there should not be any whitespace between ",", it should be one string.
yii fixture users,users_profiles
// apply several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture User,UserProfile
// apply all fixtures
yii fixture/apply all
......@@ -68,29 +74,31 @@ yii fixture/apply all
// same as above
yii fixture all
// apply fixtures to the table users, but fixtures will be taken from different path.
yii fixture users --fixturePath='@app/my/custom/path/to/fixtures'
// apply fixtures, but for other database connection.
yii fixtures User --db='customDbConnectionId'
// apply fixtures to the table users, but for other database connection.
yii fixtures users --db='customDbConnectionId'
// apply fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
yii fixtures User --namespace='alias\my\custom\namespace'
```
Clearing tables
---------------
Unloading fixtures
------------------
To clear table, run the following command:
To unload fixture, run the following command:
```
// clear given table: delete all data and reset sequence.
yii fixture/clear users
// unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture).
yii fixture/clear User
// clear several tables. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear users,users_profile
// Unload several fixtures. Note that there should not be any whitespace between ",", it should be one string.
yii fixture/clear User,UserProfile
// clear all tables of current connection in current schema
// unload all fixtures
yii fixture/clear all
```
Same command options like: `db`, `namespace` also can be applied to this command.
Configure Command Globally
--------------------------
While command line options allow us to configure the migration command
......@@ -100,9 +108,9 @@ different migration path as follows:
```
'controllerMap' => [
'fixture' => [
'class' => 'yii\console\FixtureController',
'fixturePath' => '@app/my/custom/path/to/fixtures',
'class' => 'yii\console\controllers\FixtureController',
'db' => 'customDbConnectionId',
'namespace' => 'myalias\some\custom\namespace',
],
]
```
......
......@@ -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.
> 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`.
### Defaults
......
Database basics
===============
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/book.pdo.php). It provides
uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS:
- [MySQL](http://www.mysql.com/)
......
......@@ -3,36 +3,95 @@ Events
TBD, see also [Component.md](../api/base/Component.md).
There is no longer the need to define an `on`-method in order to define an event in Yii 2.0.
Instead, you can use whatever event names. To attach a handler to an event, you should
use the `on` method now:
[[ADD INTRODUCTION]]
Creating Event Handlers
-----------------------
In Yii 1, events were defined using the `onEventName` method syntax, such as `onBeforeSave`. This is no longer necessary in Yii 2, as event handling is now assigned using the `on` method. The method's first argument is the name of the event to watch for; the second is the handling method to be called when that event occurs:
```php
$component->on($eventName, $handler);
// To detach the handler, use:
```
[[LINK TO LIST OF EVENTS]]
The handler must be a valid PHP callback. This could be represented as:
* The name of a global function
* An array consisting of a model name and method name
* An array consisting of an object and a method name
* An anonymous function
```php
// Global function:
$component->on($eventName, 'functionName');
// Model and method names:
$component->on($eventName, ['Modelname', 'functionName']);
// Object and method name:
$component->on($eventName, [$obj, 'functionName']);
// Anonymous function:
$component->on($eventName, function($event) {
// Use $event.
});
```
As shown in the anonymous function example, the event handling function must be defined so that it takes one argument. This will be an [[Event]] object.
Removing Event Handlers
-----------------------
The correspondoing `off` method removes an event handler:
```php
// $component->off($eventName);
```
Yii supports the ability to associate multiple handlers with the same event. When using `off` as in the above, every handler is removed. To remove only a specific handler, provide that as the second argument to `off`:
```php
// $component->off($eventName, $handler);
```
The `$handler` should be presented in the `off` method in the same way as was presented in `on` in order to remove it.
Event Parameters
----------------
When you attach a handler, you can now associate it with some parameters which can be later
accessed via the event parameter by the handler:
You can make your event handlers easier to work with and more powerful by passing additional values as parameters.
```php
$component->on($eventName, $handler, $params);
```
The passed parameters will be available in the event handler through `$event->data`, which will be an array.
Because of this change, you can now use "global" events. Simply trigger and attach handlers to
an event of the application instance:
[[NEED TO CONFIRM THE ABOVE]]
Global Events
-------------
Thanks to the change in Yii 2 as to how event handlers are created, you can now use "global" events. To create a global event, simply attach handlers to an event on the application instance:
```php
Yii::$app->on($eventName, $handler);
....
// this will trigger the event and cause $handler to be invoked.
```
You can use the `trigger` method to trigger these events manually:
```php
// this will trigger the event and cause $handler to be invoked:
Yii::$app->trigger($eventName);
```
If you need to handle all instances of a class instead of the object you can attach a handler like the following:
Class Events
------------
You can also attach event handlers to all instances of a class instead of individual instances. To do so, use the static `Event::on` method:
```php
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
......
......@@ -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
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:
......@@ -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
......@@ -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.
TBD: provided classes overview.
......@@ -15,8 +15,8 @@ PHP 5.4.0 or greater.
For developers who want to use Yii, understanding object-oriented
programming (OOP) is very helpful, because Yii is a pure OOP framework.
Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php)
so you should be familiar with how they work.
Yii 2.0 also makes use of the latest features of PHP such as [namespaces](http://www.php.net/manual/en/language.namespaces.php),
so you should be familiar with how they work, too.
What is Yii Best for?
......@@ -24,16 +24,16 @@ What is Yii Best for?
Yii is a generic Web programming framework that can be used for developing
virtually any type of Web application. Because it is light-weight and
equipped with sophisticated caching mechanisms, it is especially suited
to high-traffic applications, such as portals, forums, content
management systems (CMS), e-commerce projects, etc.
equipped with sophisticated caching mechanisms, Yii is especially suited
to high-traffic applications such as portals, forums, content
management systems (CMS), e-commerce projects, and so on.
How does Yii Compare with Other Frameworks?
-------------------------------------------
- Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach.
- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching etc.
- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching, etc.
- Yii strikes a good balance between simplicity and features.
- Syntax and overall development usability are taken seriously by the Yii development team.
- Performance is one of the key goals for the Yii framework.
......
......@@ -116,7 +116,7 @@ use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase
{
protected function fixtures()
public function fixtures()
{
return [
'profiles' => UserProfileFixture::className(),
......@@ -175,6 +175,43 @@ This means you only need to work with `@app/tests/fixtures/initdb.php` if you wa
before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures.
Organizing Fixture Classes and Data Files
-----------------------------------------
By default, fixture classes look for the corresponding data files under the `data` folder which is a sub-folder
of the folder containing the fixture class files. You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch different data files for the same fixture class for
different tests. We thus recommend that you organize the data files in a hierarchical way that is similar to
your class namespaces. For example,
```
# under folder tests\unit\fixtures
data\
components\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
```
In this way you will avoid collision of fixture data files between tests and use them as you need.
> Note: In the example above fixture files are named only for example purpose. In real life you should name them
> according to which fixture class your fixture classes are extending from. For example, if you are extending
> from [[\yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names;
> If you are extending for [[\yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names.
The similar hierarchy can be used to organize fixture class files. Instead of using `data` as the root directory, you may
want to use `fixtures` as the root directory to avoid conflict with the data files.
Summary
-------
......@@ -186,5 +223,5 @@ of running unit tests related with DB:
- Load fixtures: clean up the relevant DB tables and populate them with fixture data;
- Perform the actual test;
- Unload fixtures.
3. Repeat 2 until all tests finish.
3. Repeat Step 2 until all tests finish.
......@@ -223,7 +223,7 @@ This method also determines which attributes are safe and which are not. In part
given a scenario, if an attribute appears in the corresponding attribute list in [[yii\base\Model::scenarios()|scenarios()]]
and the name is not prefixed with `!`, it is considered *safe*.
Because of the above change, Yii 2.0 no longer has "safe" and "unsafe" validators.
Because of the above change, Yii 2.0 no longer has "unsafe" validator.
If your model only has one scenario (very common), you do not have to overwrite [[yii\base\Model::scenarios()|scenarios()]],
and everything will still work like the 1.1 way.
......
......@@ -186,7 +186,7 @@ class Renderer extends \yii\apidoc\templates\html\Renderer
protected function fixMarkdownLinks($content)
{
$content = preg_replace('/href\s*=\s*"([^"]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content);
$content = preg_replace('/href\s*=\s*"([^"\/]+)\.md(#.*)?"/i', 'href="guide_\1.html\2"', $content);
return $content;
}
}
\ No newline at end of file
......@@ -232,6 +232,7 @@ abstract class Renderer extends BaseRenderer implements ViewContextInterface
*/
public function renderInheritance($class)
{
$parents = [];
$parents[] = $this->typeLink($class);
while ($class->parentClass !== null) {
if(isset($this->context->classes[$class->parentClass])) {
......
......@@ -29,6 +29,8 @@ class Dropdown extends Widget
* - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - linkOptions: array, optional, the HTML attributes of the item link.
* - options: array, optional, the HTML attributes of the item.
* - items: array, optional, the submenu items. The structure is the same as this property.
* Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
*
* To insert divider use `<li role="presentation" class="divider"></li>`.
*/
......@@ -84,6 +86,10 @@ class Dropdown extends Widget
$linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
$linkOptions['tabindex'] = '-1';
$content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
if (!empty($item['items'])) {
$content .= $this->renderItems($item['items']);
Html::addCssClass($options, 'dropdown-submenu');
}
$lines[] = Html::tag('li', $content, $options);
}
......
......@@ -18,7 +18,7 @@ class DbTestCase extends TestCase
/**
* @inheritdoc
*/
protected function globalFixtures()
public function globalFixtures()
{
return [
InitDbFixture::className(),
......
......@@ -5,6 +5,9 @@ namespace yii\codeception;
use Yii;
use yii\base\InvalidConfigException;
use Codeception\TestCase\Test;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
use yii\test\ActiveFixture;
use yii\test\FixtureTrait;
/**
......@@ -26,12 +29,52 @@ class TestCase extends Test
public $appConfig = '@tests/unit/_config.php';
/**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/**
* @inheritdoc
*/
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->unloadFixtures();
$this->loadFixtures();
}
......@@ -40,7 +83,6 @@ class TestCase extends Test
*/
protected function tearDown()
{
$this->unloadFixtures();
$this->destroyApplication();
parent::tearDown();
}
......
......@@ -9,6 +9,8 @@ namespace yii\debug;
use yii\web\AssetBundle;
/**
* Debugger asset bundle
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
......
......@@ -72,6 +72,13 @@ class LogTarget extends Target
$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)
{
touch($indexFile);
......
......@@ -11,6 +11,7 @@ use Yii;
use yii\base\Application;
use yii\web\View;
use yii\web\ForbiddenHttpException;
use yii\helpers\ArrayHelper;
/**
* The Yii Debug Module provides the debug toolbar and debugger
......@@ -50,7 +51,9 @@ class Module extends \yii\base\Module
*/
public $historySize = 50;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
......@@ -61,7 +64,7 @@ class Module extends \yii\base\Module
Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
});
$this->panels = array_merge($this->corePanels(), $this->panels);
$this->panels = ArrayHelper::merge($this->corePanels(), $this->panels);
foreach ($this->panels as $id => $config) {
$config['module'] = $this;
$config['id'] = $id;
......@@ -69,13 +72,16 @@ class Module extends \yii\base\Module
}
}
/**
* @inheritdoc
*/
public function beforeAction($action)
{
Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']);
unset(Yii::$app->getLog()->targets['debug']);
$this->logTarget = null;
if ($this->checkAccess($action)) {
if ($this->checkAccess()) {
return parent::beforeAction($action);
} elseif ($action->id === 'toolbar') {
return false;
......@@ -84,6 +90,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)
{
if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
......@@ -99,6 +110,10 @@ class Module extends \yii\base\Module
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()
{
$ip = Yii::$app->getRequest()->getUserIP();
......@@ -111,6 +126,9 @@ class Module extends \yii\base\Module
return false;
}
/**
* @return array default set of panels
*/
protected function corePanels()
{
return [
......
......@@ -73,6 +73,11 @@ class Panel extends Component
return null;
}
/**
* Loads data into the panel
*
* @param mixed $data
*/
public function load($data)
{
$this->data = $data;
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search;
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
{
/**
* @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
*/
protected $rules = [];
/**
* Adds rules for filtering data. Match can be partial or exactly.
* Adds data filtering rule.
*
* @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) {
return;
if ($rule->hasValue()) {
$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
* @return array filtered data
*/
......@@ -36,7 +47,7 @@ class Filter extends Component
$filtered = [];
foreach ($data as $row) {
if ($this->checkFilter($row)) {
if ($this->passesFilter($row)) {
$filtered[] = $row;
}
}
......@@ -45,28 +56,25 @@ class Filter extends Component
}
/**
* Check if the given data satisfies filters.
* @param array $row
* Checks if the given data satisfies filters.
*
* @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) {
if (isset($this->rules[$name])) {
#check all rules for given attribute
// check all rules for a given attribute
foreach ($this->rules[$name] as $rule) {
if (!$rule->check($value)) {
$matched = false;
/** @var MatcherInterface $rule */
if (!$rule->match($value)) {
return false;
}
}
}
}
return $matched;
return true;
}
}
......@@ -5,22 +5,36 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matches;
namespace yii\debug\components\search\matchers;
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>
* @since 2.0
*/
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 @@
* @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>
* @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.
* @param mixed $value
* @inheritdoc
*/
public function check($value)
public function match($value)
{
return ($value > $this->value);
return ($value > $this->baseValue);
}
}
......@@ -5,23 +5,21 @@
* @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>
* @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.
* @param mixed $value
* @inheritdoc
*/
public function check($value)
public function match($value)
{
return ($value < $this->value);
return ($value < $this->baseValue);
}
}
......@@ -5,21 +5,35 @@
* @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>
* @since 2.0
*/
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
*/
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 @@
* @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>
* @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;
/**
* Checks if the given value is the same as base one or has partial match with base one.
* @param mixed $value
* @inheritdoc
*/
public function check($value)
public function match($value)
{
if (!$this->partial) {
return (mb_strtolower($this->value, 'utf8') == mb_strtolower($value, 'utf8'));
return (mb_strtolower($this->baseValue, 'utf8') == mb_strtolower($value, 'utf8'));
} 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;
use yii\debug\models\search\Debug;
/**
* Debugger controller
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DefaultController extends Controller
{
/**
* @inheritdoc
*/
public $layout = 'main';
/**
* @var \yii\debug\Module
......@@ -28,6 +33,9 @@ class DefaultController extends Controller
*/
public $summary;
/**
* @inheritdoc
*/
public function actions()
{
$actions = [];
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\base\Model;
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
{
/**
* @param Filter $filter
* @param string $attribute
* @param boolean $partial
* Adds filtering condition for a given attribute
*
* @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;
if (mb_strpos($value, '>') !== false) {
$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) {
$value = intval(str_replace('<', '', $value));
$filter->addMatch($attribute, new matches\Lower(['value' => $value]));
$filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value]));
} else {
$filter->addMatch($attribute, new matches\Exact(['value' => $value, 'partial' => $partial]));
$filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial]));
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
......@@ -6,13 +11,16 @@ use yii\data\ArrayDataProvider;
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
{
/**
* @var string type attribute input search value
* @var string type of the input search value
*/
public $type;
......@@ -21,6 +29,9 @@ class Db extends Base
*/
public $query;
/**
* @inheritdoc
*/
public function rules()
{
return [
......@@ -41,8 +52,9 @@ class Db extends Base
/**
* 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
*/
public function search($params, $models)
......@@ -69,5 +81,4 @@ class Db extends Base
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
......@@ -6,7 +11,11 @@ use yii\data\ArrayDataProvider;
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
{
......@@ -51,6 +60,9 @@ class Debug extends Base
*/
public $criticalCodes = [400, 404, 500];
/**
* @inheritdoc
*/
public function rules()
{
return [
......@@ -70,14 +82,14 @@ class Debug extends Base
'ajax' => 'Ajax',
'url' => 'url',
'statusCode' => 'Status code',
'sqlCount' => 'Total queries count',
'sqlCount' => 'Total queries',
];
}
/**
* 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
*/
public function search($params, $models)
......@@ -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
* @return bool
* @return boolean
*/
public function isCodeCritical($code)
{
return in_array($code, $this->criticalCodes);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
......@@ -6,11 +11,14 @@ use yii\data\ArrayDataProvider;
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
{
/**
* @var string ip attribute input search value
*/
......@@ -26,6 +34,9 @@ class Log extends Base
*/
public $message;
/**
* @inheritdoc
*/
public function rules()
{
return [
......@@ -47,8 +58,9 @@ class Log extends Base
/**
* 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
*/
public function search($params, $models)
......@@ -73,5 +85,4 @@ class Log extends Base
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
......@@ -6,11 +11,14 @@ use yii\data\ArrayDataProvider;
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
{
/**
* @var string method attribute input search value
*/
......@@ -21,6 +29,9 @@ class Profile extends Base
*/
public $info;
/**
* @inheritdoc
*/
public function rules()
{
return [
......@@ -41,8 +52,9 @@ class Profile extends Base
/**
* 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
*/
public function search($params, $models)
......@@ -69,5 +81,4 @@ class Profile extends Base
return $dataProvider;
}
}
......@@ -18,26 +18,45 @@ use yii\debug\Panel;
*/
class ConfigPanel extends Panel
{
/**
* @inheritdoc
*/
public function getName()
{
return 'Configuration';
}
/**
* Returns Yii logo ready to use in `<img src="`
*
* @return string base64 representation of the image
*/
public static function getYiiLogo()
{
return 'data:image/png;base64,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';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/config/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/config/detail', ['panel' => $this]);
}
/**
* Returns data about extensions
*
* @return array
*/
public function getExtensions()
{
$data = [];
......@@ -47,6 +66,9 @@ class ConfigPanel extends Panel
return $data;
}
/**
* @inheritdoc
*/
public function save()
{
return [
......
......@@ -20,7 +20,12 @@ use yii\debug\models\search\Db;
*/
class DbPanel extends Panel
{
/**
* @var integer the threshold for determining whether the request has involved
* critical number of DB queries. If the number of queries exceeds this number,
* the execution is considered taking critical number of DB queries.
*/
public $criticalQueryThreshold;
/**
* @var array db queries info extracted to array as models, to use with data provider.
*/
......@@ -31,11 +36,17 @@ class DbPanel extends Panel
*/
private $_timings;
/**
* @inheritdoc
*/
public function getName()
{
return 'Database';
}
/**
* @inheritdoc
*/
public function getSummary()
{
$timings = $this->calculateTimings();
......@@ -50,6 +61,9 @@ class DbPanel extends Panel
]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Db();
......@@ -63,7 +77,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]
*/
protected function calculateTimings()
......@@ -74,6 +89,9 @@ class DbPanel extends Panel
return $this->_timings;
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
......@@ -82,7 +100,8 @@ class DbPanel extends Panel
}
/**
* Returns total queries time.
* Returns total query time.
*
* @param array $timings
* @return integer total time
*/
......@@ -98,8 +117,8 @@ class DbPanel extends Panel
}
/**
* Returns array of models that represents logs of the current request. Can be used with data providers,
* like yii\data\ArrayDataProvider.
* Returns an array of models that represents logs of the current request.
* Can be used with data providers such as \yii\data\ArrayDataProvider.
* @return array models
*/
protected function getModels()
......@@ -110,7 +129,7 @@ class DbPanel extends Panel
foreach($timings as $seq => $dbTiming) {
$this->_models[] = [
'type' => $this->detectQueryType($dbTiming['info']),
'type' => $this->getQueryType($dbTiming['info']),
'query' => $dbTiming['info'],
'duration' => ($dbTiming['duration'] * 1000), // in milliseconds
'trace' => $dbTiming['trace'],
......@@ -123,16 +142,27 @@ class DbPanel extends Panel
}
/**
* Detects databse timing type. Detecting is produced through simple parsing to the first space|tab|new row.
* First word before space is timing type. If there is no such words, timing will have empty type.
* Returns databse query type.
*
* @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);
preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? $matches[0] : '';
}
/**
* Check if given queries count is critical according settings.
*
* @param integer $count queries count
* @return boolean
*/
public function isQueryCountCritical($count)
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
}
......@@ -20,22 +20,30 @@ use yii\debug\models\search\Log;
*/
class LogPanel extends Panel
{
/**
* @var array log messages extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @inheritdoc
*/
public function getName()
{
return 'Logs';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Log();
......@@ -48,6 +56,9 @@ class LogPanel extends Panel
]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
......@@ -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,
* like yii\data\ArrayDataProvider.
* @param boolean $refresh if needed to build models from log messages and refresh them.
* Returns an array of models that represents logs of the current request.
* Can be used with data providers, such as \yii\data\ArrayDataProvider.
*
* @param boolean $refresh if need to build models from log messages and refresh them.
* @return array models
*/
protected function getModels($refresh = false)
......@@ -78,5 +90,4 @@ class LogPanel extends Panel
}
return $this->_models;
}
}
......@@ -25,11 +25,17 @@ class ProfilingPanel extends Panel
*/
private $_models;
/**
* @inheritdoc
*/
public function getName()
{
return 'Profiling';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/profile/summary', [
......@@ -39,6 +45,9 @@ class ProfilingPanel extends Panel
]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Profile();
......@@ -53,6 +62,9 @@ class ProfilingPanel extends Panel
]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
......@@ -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
*/
protected function getModels()
......@@ -87,5 +99,4 @@ class ProfilingPanel extends Panel
}
return $this->_models;
}
}
......@@ -19,24 +19,37 @@ use yii\debug\Panel;
*/
class RequestPanel extends Panel
{
/**
* @inheritdoc
*/
public function getName()
{
return 'Request';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/request/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/request/detail', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function save()
{
$headers = Yii::$app->getRequest()->getHeaders();
$requestHeaders = [];
foreach ($headers as $name => $value) {
if (is_array($value) && count($value) == 1) {
$requestHeaders[$name] = current($value);
......@@ -95,5 +108,4 @@ class RequestPanel extends Panel
'SESSION' => empty($_SESSION) ? [] : $_SESSION,
];
}
}
......@@ -32,7 +32,9 @@ echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) {
if ($searchModel->isCodeCritical($model['statusCode'])) {
$dbPanel = $this->context->module->panels['db'];
if ($searchModel->isCodeCritical($model['statusCode']) || $dbPanel->isQueryCountCritical($model['sqlCount'])) {
return ['class'=>'danger'];
} else {
return [];
......@@ -58,7 +60,22 @@ echo GridView::widget([
'ip',
[
'attribute' => 'sqlCount',
'label' => 'Total queries count'
'label' => 'Total queries',
'value' => function ($data) {
$dbPanel = $this->context->module->panels['db'];
if ($dbPanel->isQueryCountCritical($data['sqlCount'])) {
$content = Html::tag('b', $data['sqlCount']) . ' ' . Html::tag('span','',['class' => 'glyphicon glyphicon-exclamation-sign']);
return Html::a($content, $dbPanel->getUrl(), [
'title' => 'Too many queries. Allowed count is ' . $dbPanel->criticalQueryThreshold,
]);
} else {
return $data['sqlCount'];
}
},
'format' => 'html',
],
[
'attribute' => 'method',
......
<?php
use yii\helpers\Html;
use yii\grid\GridView;
use yii\data\ArrayDataProvider;
use yii\log\Logger;
?>
<h1>Log Messages</h1>
......
......@@ -33,4 +33,3 @@ echo Tabs::widget([
],
],
]);
?>
......@@ -152,7 +152,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($result);
$model = $class::instantiate($result);
$class::populateRecord($model, $result);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -94,7 +94,8 @@ class ActiveRecord extends BaseActiveRecord
$command = static::getDb()->createCommand();
$result = $command->get(static::index(), static::type(), $primaryKey, $options);
if ($result['exists']) {
$model = static::create($result);
$model = static::instantiate($result);
static::populateRecord($model, $result);
$model->afterFind();
return $model;
}
......@@ -123,7 +124,8 @@ class ActiveRecord extends BaseActiveRecord
$models = [];
foreach($result['docs'] as $doc) {
if ($doc['exists']) {
$model = static::create($doc);
$model = static::instantiate($doc);
static::populateRecord($model, $doc);
$model->afterFind();
$models[] = $model;
}
......@@ -264,22 +266,38 @@ class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an active record object using a row of data.
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
* @inheritdoc
*/
public static function create($row)
public static function populateRecord($record, $row)
{
$record = parent::create($row['_source']);
parent::populateRecord($record, $row['_source']);
$pk = static::primaryKey()[0];
if ($pk === '_id') {
$record->$pk = $row['_id'];
$record->_id = $row['_id'];
}
$record->_score = isset($row['_score']) ? $row['_score'] : null;
$record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available...
return $record;
}
/**
* Creates an active record instance.
*
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
*
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $row row data to be populated into the record.
* This array consists of the following keys:
* - `_source`: refers to the attributes of the record.
* - `_type`: the type this record is stored in.
* - `_index`: the index this record is stored in.
* @return static the newly created active record
*/
public static function instantiate($row)
{
return new static;
}
/**
......
......@@ -5,9 +5,11 @@ Yii Framework 2 elasticsearch extension Change Log
----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1313: made index and type available in `ActiveRecord::instantiate()` to allow creating records based on elasticsearch type when doing cross index/type search (cebe)
- Enh #1382: Added a debug toolbar panel for elasticsearch (cebe)
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -165,8 +165,7 @@ class Query extends Component implements QueryInterface
*/
public function one($db = null)
{
$options['size'] = 1;
$result = $this->createCommand($db)->search($options);
$result = $this->createCommand($db)->search(['size' => 1]);
if (empty($result['hits']['hits'])) {
return false;
}
......
......@@ -9,8 +9,8 @@ namespace yii\faker;
use Yii;
use yii\console\Exception;
use yii\helpers\FileHelper;
use yii\helpers\Console;
use yii\helpers\FileHelper;
/**
* This command manage fixtures creations based on given template.
......
......@@ -123,6 +123,11 @@ class CodeFile extends Object
}
}
/**
* Returns preview or false if it cannot be rendered
*
* @return boolean|string
*/
public function preview()
{
if (($pos = strrpos($this->path, '.')) !== false) {
......@@ -140,6 +145,11 @@ class CodeFile extends Object
}
}
/**
* Returns diff or false if it cannot be calculated
*
* @return boolean|string
*/
public function diff()
{
$type = strtolower($this->getType());
......@@ -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)
{
if (!is_array($lines1)) {
......
......@@ -190,7 +190,6 @@ abstract class Generator extends Model
public function loadStickyAttributes()
{
$stickyAttributes = $this->stickyAttributes();
$attributes[] = 'template';
$path = $this->getStickyDataFile();
if (is_file($path)) {
$result = json_decode(file_get_contents($path), true);
......
......@@ -21,6 +21,9 @@ class ActiveField extends \yii\widgets\ActiveField
*/
public $model;
/**
* @inheritdoc
*/
public function init()
{
$stickyAttributes = $this->model->stickyAttributes();
......
......@@ -107,6 +107,9 @@ class DefaultController extends Controller
}
}
/**
* @inheritdoc
*/
public function createUrl($route, $params = [])
{
if (!isset($params['id']) && $this->generator !== null) {
......@@ -120,6 +123,13 @@ class DefaultController extends Controller
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 = [])
{
foreach ($this->module->generators as $id => $generator) {
......
......@@ -33,17 +33,26 @@ class Generator extends \yii\gii\Generator
public $indexWidgetType = 'grid';
public $searchModelClass;
/**
* @inheritdoc
*/
public function getName()
{
return 'CRUD Generator';
}
/**
* @inheritdoc
*/
public function getDescription()
{
return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete)
operations for the specified data model.';
}
/**
* @inheritdoc
*/
public function rules()
{
return array_merge(parent::rules(), [
......@@ -61,6 +70,9 @@ class Generator extends \yii\gii\Generator
]);
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return array_merge(parent::attributeLabels(), [
......@@ -94,6 +106,9 @@ class Generator extends \yii\gii\Generator
];
}
/**
* @inheritdoc
*/
public function requiredTemplates()
{
return ['controller.php'];
......@@ -107,6 +122,9 @@ class Generator extends \yii\gii\Generator
return ['baseControllerClass', 'moduleID', 'indexWidgetType'];
}
/**
* Checks if model class is valid
*/
public function validateModelClass()
{
/** @var ActiveRecord $class */
......@@ -117,6 +135,9 @@ class Generator extends \yii\gii\Generator
}
}
/**
* Checks if model ID is valid
*/
public function validateModuleID()
{
if (!empty($this->moduleID)) {
......@@ -184,6 +205,7 @@ class Generator extends \yii\gii\Generator
}
/**
* Generates code for active field
* @param string $attribute
* @return string
*/
......@@ -217,6 +239,7 @@ class Generator extends \yii\gii\Generator
}
/**
* Generates code for active search field
* @param string $attribute
* @return string
*/
......@@ -235,6 +258,7 @@ class Generator extends \yii\gii\Generator
}
/**
* Generates column format
* @param \yii\db\ColumnSchema $column
* @return string
*/
......@@ -298,6 +322,9 @@ class Generator extends \yii\gii\Generator
return $rules;
}
/**
* @return array searchable attributes
*/
public function getSearchAttributes()
{
return $this->getColumnNames();
......@@ -309,6 +336,7 @@ class Generator extends \yii\gii\Generator
*/
public function generateSearchLabels()
{
/** @var \yii\base\Model $model */
$model = new $this->modelClass();
$attributeLabels = $model->attributeLabels();
$labels = [];
......@@ -330,11 +358,16 @@ class Generator extends \yii\gii\Generator
return $labels;
}
/**
* Generates search conditions
* @return array
*/
public function generateSearchConditions()
{
$columns = [];
if (($table = $this->getTableSchema()) === false) {
$class = $this->modelClass;
/** @var \yii\base\Model $model */
$model = new $class();
foreach ($model->attributes() as $attribute) {
$columns[$attribute] = 'unknown';
......@@ -369,6 +402,10 @@ class Generator extends \yii\gii\Generator
return $conditions;
}
/**
* Generates URL parameters
* @return string
*/
public function generateUrlParams()
{
/** @var ActiveRecord $class */
......@@ -385,6 +422,10 @@ class Generator extends \yii\gii\Generator
}
}
/**
* Generates action parameters
* @return string
*/
public function generateActionParams()
{
/** @var ActiveRecord $class */
......@@ -397,6 +438,10 @@ class Generator extends \yii\gii\Generator
}
}
/**
* Generates parameter tags for phpdoc
* @return array parameter tags for phpdoc
*/
public function generateActionParamComments()
{
/** @var ActiveRecord $class */
......@@ -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()
{
/** @var ActiveRecord $class */
......@@ -431,6 +480,9 @@ class Generator extends \yii\gii\Generator
}
}
/**
* @return array model column names
*/
public function getColumnNames()
{
/** @var ActiveRecord $class */
......@@ -438,6 +490,7 @@ class Generator extends \yii\gii\Generator
if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
return $class::getTableSchema()->getColumnNames();
} else {
/** @var \yii\base\Model $model */
$model = new $class();
return $model->attributes();
}
......
......@@ -155,7 +155,7 @@ class BaseImage
$img = $img->thumbnail($box, $mode);
// 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
$size = $img->getSize();
......
......@@ -53,12 +53,12 @@ class ActiveFixture extends BaseActiveFixture
/**
* Loads the fixture data.
* The default implementation will first reset the DB table and then populate it with the data
* returned by [[getData()]].
* Data will be batch inserted into the given collection.
*/
public function load()
{
$this->resetCollection();
parent::load();
$data = $this->getData();
$this->getCollection()->batchInsert($data);
foreach ($data as $alias => $row) {
......@@ -66,6 +66,17 @@ class ActiveFixture extends BaseActiveFixture
}
}
/**
* Unloads the fixture.
*
* The default implementation will clean up the colection by calling [[resetCollection()]].
*/
public function unload()
{
$this->resetCollection();
parent::unload();
}
protected function getCollection()
{
return $this->db->getCollection($this->getCollectionName());
......
......@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -81,7 +81,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -112,7 +112,8 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -6,6 +6,7 @@ Yii Framework 2 redis extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -139,7 +139,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -619,29 +619,17 @@ abstract class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an active record object using a row of data.
* This method is called by [[ActiveQuery]] to populate the query results
* into Active Records. It is not meant to be used to create new records.
* @param array $row attribute values (name => value)
* @return ActiveRecord the newly created active record.
* @inheritdoc
*/
public static function create($row)
public static function populateRecord($record, $row)
{
$record = static::instantiate($row);
$columns = static::getIndexSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$column = $columns[$name];
if ($column->isMva) {
$value = explode(',', $value);
}
$record->setAttribute($name, $value);
} else {
$record->$name = $value;
if (isset($columns[$name]) && $columns[$name]->isMva) {
$row[$name] = explode(',', $value);
}
}
$record->setOldAttributes($record->getAttributes());
return $record;
parent::populateRecord($record, $row);
}
/**
......
......@@ -7,6 +7,7 @@ Yii Framework 2 sphinx extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -6,6 +6,7 @@ Yii Framework 2 Change Log
- 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 #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 #1497: Localized view files are not correctly returned (mintao)
- Bug #1500: Log messages exported to files are not separated by newlines (omnilight, qiangxue)
......@@ -114,6 +115,7 @@ Yii Framework 2 Change Log
- Enh: Added `yii\web\View::POS_LOAD` (qiangxue)
- Enh: Added `yii\web\Response::clearOutputBuffers()` (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)
- 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)
......@@ -139,6 +141,8 @@ Yii Framework 2 Change Log
- 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 #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 #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
......
......@@ -515,7 +515,7 @@ abstract class Application extends Module
$handler->handle($exception);
} else {
echo $this->renderException($exception);
if (PHP_SAPI === 'cli') {
if (PHP_SAPI === 'cli' && !YII_ENV_TEST) {
exit(1);
}
}
......
......@@ -87,7 +87,10 @@ class ErrorHandler extends Component
{
if (Yii::$app instanceof \yii\console\Application || YII_ENV_TEST) {
echo Yii::$app->renderException($exception);
exit(1);
if (!YII_ENV_TEST) {
exit(1);
}
return;
}
$useErrorView = !YII_DEBUG || $exception instanceof UserException;
......
......@@ -45,7 +45,7 @@ use yii\validators\Validator;
* property is read-only.
* @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
* 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.
* This property is read-only.
*
......@@ -57,7 +57,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
/**
* 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
......@@ -80,7 +80,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
/**
* @var string current scenario
*/
private $_scenario = self::DEFAULT_SCENARIO;
private $_scenario = self::SCENARIO_DEFAULT;
/**
* Returns the validation rules for attributes.
......@@ -170,7 +170,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
* 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()]]
* 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
* are being validated by the validation rules that apply to the scenario.
*
......@@ -178,7 +178,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
*/
public function scenarios()
{
$scenarios = [self::DEFAULT_SCENARIO => []];
$scenarios = [self::SCENARIO_DEFAULT => []];
foreach ($this->getValidators() as $validator) {
foreach ($validator->on as $scenario) {
$scenarios[$scenario] = [];
......@@ -214,7 +214,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
}
foreach ($scenarios as $scenario => $attributes) {
if (empty($attributes) && $scenario !== self::DEFAULT_SCENARIO) {
if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) {
unset($scenarios[$scenario]);
} else {
$scenarios[$scenario] = array_keys($attributes);
......@@ -649,7 +649,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
* Scenario affects how validation is performed and which attributes can
* 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()
{
......
......@@ -144,7 +144,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
}
if (!empty($this->with)) {
$models = [$model];
......
......@@ -128,11 +128,14 @@ trait ActiveQueryTrait
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$models[] = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
$models[] = $model;
}
} else {
foreach ($rows as $row) {
$model = $class::create($row);
$model = $class::instantiate($row);
$class::populateRecord($model, $row);
if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy};
} else {
......
......@@ -275,6 +275,20 @@ class ActiveRecord extends BaseActiveRecord
}
/**
* @inheritdoc
*/
public static function populateRecord($record, $row)
{
$columns = static::getTableSchema()->columns;
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$row[$name] = $columns[$name]->typecast($value);
}
}
parent::populateRecord($record, $row);
}
/**
* Inserts a row into the associated database table using the attribute values of this record.
*
* This method performs the following steps in order:
......
......@@ -984,21 +984,21 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
/**
* Creates an active record object using a row of data from the database/storage.
* Populates an active record object using a row of data from the database/storage.
*
* It is an internal method meant to be called to create active record objects after
* This is an internal method meant to be called to create active record objects after
* fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
* the query results into Active Records.
* the query results into active records.
*
* When calling this method manually you should call [[afterFind()]] on the created
* record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
*
* @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
* created by [[instantiate()]] beforehand.
* @param array $row attribute values (name => value)
* @return static the newly created active record.
*/
public static function create($row)
public static function populateRecord($record, $row)
{
$record = static::instantiate($row);
$columns = array_flip($record->attributes());
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
......@@ -1008,12 +1008,13 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
}
$record->_oldAttributes = $record->_attributes;
return $record;
}
/**
* Creates an active record instance.
* This method is called by [[create()]].
*
* This method is called together with [[populateRecord()]] by [[ActiveQuery]].
*
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
......
......@@ -409,15 +409,12 @@ abstract class Schema extends Object
static $typeMap = [ // abstract type => php type
'smallint' => 'integer',
'integer' => 'integer',
'bigint' => 'integer',
'boolean' => 'boolean',
'float' => 'double',
];
if (isset($typeMap[$column->type])) {
if ($column->type === 'bigint') {
return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string';
} elseif ($column->type === 'integer') {
return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer';
if ($column->type === 'integer') {
return $column->unsigned ? 'string' : 'integer';
} else {
return $typeMap[$column->type];
}
......
......@@ -111,9 +111,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
$value = (int)$value - 1;
}
try {
// it's possible sqlite_sequence does not exist
$db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute();
} catch (Exception $e) {
// it's possible that sqlite_sequence does not exist
}
} elseif ($table === null) {
throw new InvalidParamException("Table not found: $tableName");
......
......@@ -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.
*
* 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>
* @since 2.0
*/
......
......@@ -13,6 +13,19 @@ use yii\helpers\Html;
/**
* 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
* obtained by calling the following JavaScript code:
*
......
......@@ -67,9 +67,9 @@ class GridView extends BaseListView
* 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:
*
* ~~~php
* ```php
* function ($model, $key, $index, $grid)
* ~~~
* ```
*
* - `$model`: the current data model being rendered
* - `$key`: the key value associated with the current data model
......@@ -111,7 +111,7 @@ class GridView extends BaseListView
* @var array grid column configuration. Each array element represents the configuration
* for one particular grid column. For example,
*
* ~~~php
* ```php
* [
* ['class' => SerialColumn::className()],
* [
......@@ -122,7 +122,7 @@ class GridView extends BaseListView
* ],
* ['class' => CheckboxColumn::className()],
* ]
* ~~~
* ```
*
* If a column is of class [[DataColumn]], the "class" element can be omitted.
*
......
......@@ -10,6 +10,18 @@ namespace yii\grid;
/**
* 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>
* @since 2.0
*/
......
......@@ -422,7 +422,7 @@ class BaseConsole
$styleA = ArrayHelper::merge($styleA, $style);
}
$styleString[] = [];
$styleString = [];
foreach ($styleA as $name => $content) {
if ($name === 'text-decoration') {
$content = implode(' ', $content);
......
......@@ -137,7 +137,7 @@ class MessageFormatter extends Component
}
// replace named arguments
if (($tokens = $this->tokenizePattern($pattern)) === false) {
if (($tokens = self::tokenizePattern($pattern)) === false) {
$this->_errorCode = -1;
$this->_errorMessage = "Message pattern is invalid.";
return false;
......@@ -187,7 +187,7 @@ class MessageFormatter extends Component
*/
private function replaceNamedArguments($pattern, $givenParams, &$resultingParams, &$map = [])
{
if (($tokens = $this->tokenizePattern($pattern)) === false) {
if (($tokens = self::tokenizePattern($pattern)) === false) {
return false;
}
foreach($tokens as $i => $token) {
......@@ -214,7 +214,7 @@ class MessageFormatter extends Component
if (!isset($token[2])) {
return false;
}
$subtokens = $this->tokenizePattern($token[2]);
$subtokens = self::tokenizePattern($token[2]);
$c = count($subtokens);
for ($k = 0; $k + 1 < $c; $k++) {
if (is_array($subtokens[$k]) || !is_array($subtokens[++$k])) {
......@@ -239,7 +239,7 @@ class MessageFormatter extends Component
*/
protected function fallbackFormat($pattern, $args, $locale)
{
if (($tokens = $this->tokenizePattern($pattern)) === false) {
if (($tokens = self::tokenizePattern($pattern)) === false) {
$this->_errorCode = -1;
$this->_errorMessage = "Message pattern is invalid.";
return false;
......@@ -261,7 +261,7 @@ class MessageFormatter extends Component
* @param string $pattern patter to tokenize
* @return array|bool array of tokens or false on failure
*/
private function tokenizePattern($pattern)
private static function tokenizePattern($pattern)
{
$depth = 1;
if (($start = $pos = mb_strpos($pattern, '{')) === false) {
......@@ -340,7 +340,7 @@ class MessageFormatter extends Component
if (!isset($token[2])) {
return false;
}
$select = static::tokenizePattern($token[2]);
$select = self::tokenizePattern($token[2]);
$c = count($select);
$message = false;
for ($i = 0; $i + 1 < $c; $i++) {
......@@ -368,7 +368,7 @@ class MessageFormatter extends Component
if (!isset($token[2])) {
return false;
}
$plural = static::tokenizePattern($token[2]);
$plural = self::tokenizePattern($token[2]);
$c = count($plural);
$message = false;
$offset = 0;
......
......@@ -32,30 +32,30 @@ return array (
'No' => 'Жоқ',
'No help for unknown command "{command}".' => 'Анықтама белгісіз команда үшін ақиық "{command}".',
'No help for unknown sub-command "{command}".' => 'Анықтама белгісіз субкоманда үшін ақиық "{command}".',
'No results found.' => 'Ештене табылган жок.',
'Only files with these extensions are allowed: {extensions}.' => 'Файлды жуктеу тек қана осы аумақтармен: {extensions}.',
'Only files with these mimeTypes are allowed: {mimeTypes}.' => 'Файлды жуктеу тек қана осы MIME-үлгілермен: {mimeTypes}.',
'No results found.' => 'Ештене табылған жок.',
'Only files with these extensions are allowed: {extensions}.' => 'Файлды жүктеу тек қана осы аумақтармен: {extensions}.',
'Only files with these mimeTypes are allowed: {mimeTypes}.' => 'Файлды жүктеу тек қана осы MIME-үлгілермен: {mimeTypes}.',
'Page not found.' => 'Парақ табылган жок.',
'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>.',
'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 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 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 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 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 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 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 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.' => 'Тексеріс коды қате.',
'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.' => 'Берілген мәліметердің тексеру сәті болмады.',
'Unknown command "{command}".' => 'Белгісіз команда "{command}".',
'Unknown option: --{name}' => 'Белгісіз опция: --{name}',
'Update' => 'Редакциялау',
'Yes' => 'Я',
'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' => 'кіргізілген мағыналар',
'{attribute} "{value}" has already been taken.' => '{attribute} «{value}» Бұл бос емес.',
'{attribute} cannot be blank.' => 'Толтыруға қажет «{attribute}».',
......@@ -75,7 +75,7 @@ return array (
'{attribute} must be no less than {min}.' => 'Мағына «{attribute}» көп болу керек {min}.',
'{attribute} must be repeated exactly.' => 'Мағына «{attribute}» дәлме-дәл қайталану керек.',
'{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 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 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 {length, number} {length, plural, one{character} other{characters}}.' => 'Мағынада «{attribute}» болу керек {length, number} {length, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.',
);
......@@ -65,7 +65,6 @@ class ActiveFixture extends BaseActiveFixture
/**
* Loads the fixture.
*
* The default implementation will first clean up the table by calling [[resetTable()]].
* It will then populate the table with the data returned by [[getData()]].
*
* If you override this method, you should consider calling the parent implementation
......@@ -73,7 +72,7 @@ class ActiveFixture extends BaseActiveFixture
*/
public function load()
{
$this->resetTable();
parent::load();
$table = $this->getTableSchema();
......@@ -92,6 +91,17 @@ class ActiveFixture extends BaseActiveFixture
}
/**
* Unloads the fixture.
*
* The default implementation will clean up the table by calling [[resetTable()]].
*/
public function unload()
{
$this->resetTable();
parent::unload();
}
/**
* Returns the fixture data.
*
* The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
......
......@@ -67,5 +67,19 @@ class Fixture extends Component
public function unload()
{
}
/**
* This method is called BEFORE any fixture data is unloaded for the current test.
*/
public function beforeUnload()
{
}
/**
* This method is called AFTER all fixture data have been unloaded for the current test.
*/
public function afterUnload()
{
}
}
......@@ -9,8 +9,6 @@ namespace yii\test;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
/**
* FixtureTrait provides functionalities for loading, unloading and accessing fixtures for a test case.
......@@ -33,50 +31,6 @@ trait FixtureTrait
* if B depends on A.
*/
private $_fixtures;
/**
* @var array the fixture class names indexed by the corresponding fixture names (aliases).
*/
private $_fixtureAliases;
/**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/**
* Declares the fixtures that are needed by the current test case.
......@@ -101,7 +55,7 @@ trait FixtureTrait
*
* @return array the fixtures needed by the current test case
*/
protected function fixtures()
public function fixtures()
{
return [];
}
......@@ -113,100 +67,139 @@ trait FixtureTrait
* @return array the fixtures shared and required by different test cases.
* @see fixtures()
*/
protected function globalFixtures()
public function globalFixtures()
{
return [];
}
/**
* Loads the fixtures.
* This method will load the fixtures specified by `$fixtures` or [[globalFixtures()]] and [[fixtures()]].
* @param array $fixtures the fixtures to loaded. If not set, [[fixtures()]] will be loaded instead.
* @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
* the fixtures is detected.
* Loads the specified fixtures.
* This method will call [[Fixture::load()]] for every fixture object.
* @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
* the return value of [[getFixtures()]] will be used.
*/
protected function loadFixtures($fixtures = null)
public function loadFixtures($fixtures = null)
{
if ($fixtures === null) {
$fixtures = array_merge($this->globalFixtures(), $this->fixtures());
}
// normalize fixture configurations
$config = []; // configuration provided in test case
$this->_fixtureAliases = [];
foreach ($fixtures as $name => $fixture) {
if (!is_array($fixture)) {
$fixtures[$name] = $fixture = ['class' => $fixture];
} elseif (!isset($fixture['class'])) {
throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
}
$config[$fixture['class']] = $fixture;
$this->_fixtureAliases[$name] = $fixture['class'];
}
// create fixture instances
$this->_fixtures = [];
$stack = array_reverse($fixtures);
while (($fixture = array_pop($stack)) !== null) {
if ($fixture instanceof Fixture) {
$class = get_class($fixture);
unset($this->_fixtures[$class]); // unset so that the fixture is added to the last in the next line
$this->_fixtures[$class] = $fixture;
} else {
$class = $fixture['class'];
if (!isset($this->_fixtures[$class])) {
$this->_fixtures[$class] = false;
$stack[] = $fixture = Yii::createObject($fixture);
foreach ($fixture->depends as $dep) {
// need to use the configuration provided in test case
$stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
}
} elseif ($this->_fixtures[$class] === false) {
throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
}
}
$fixtures = $this->getFixtures();
}
// load fixtures
/** @var Fixture $fixture */
foreach ($this->_fixtures as $fixture) {
foreach ($fixtures as $fixture) {
$fixture->beforeLoad();
}
foreach ($this->_fixtures as $fixture) {
foreach ($fixtures as $fixture) {
$fixture->load();
}
foreach ($this->_fixtures as $fixture) {
foreach (array_reverse($fixtures) as $fixture) {
$fixture->afterLoad();
}
}
/**
* Unloads all existing fixtures.
* Unloads the specified fixtures.
* This method will call [[Fixture::unload()]] for every fixture object.
* @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified,
* the return value of [[getFixtures()]] will be used.
*/
protected function unloadFixtures()
public function unloadFixtures($fixtures = null)
{
if ($fixtures === null) {
$fixtures = $this->getFixtures();
}
/** @var Fixture $fixture */
foreach (array_reverse($this->_fixtures) as $fixture) {
foreach ($fixtures as $fixture) {
$fixture->beforeUnload();
}
$fixtures = array_reverse($fixtures);
foreach ($fixtures as $fixture) {
$fixture->unload();
}
foreach ($fixtures as $fixture) {
$fixture->afterUnload();
}
}
/**
* @return array the loaded fixtures for the current test case
* Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]].
* @return Fixture[] the loaded fixtures for the current test case
*/
protected function getFixtures()
public function getFixtures()
{
if ($this->_fixtures === null) {
$this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
}
return $this->_fixtures;
}
/**
* Returns the named fixture.
* @param string $name the fixture alias or class name
* @param string $name the fixture name. This can be either the fixture alias name, or the class name if the alias is not used.
* @return Fixture the fixture object, or null if the named fixture does not exist.
*/
protected function getFixture($name)
public function getFixture($name)
{
if ($this->_fixtures === null) {
$this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures()));
}
$name = ltrim($name, '\\');
return isset($this->_fixtures[$name]) ? $this->_fixtures[$name] : null;
}
/**
* Creates the specified fixture instances.
* All dependent fixtures will also be created.
* @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations.
* If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created.
* @return Fixture[] the created fixture instances
* @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among
* the fixtures is detected.
*/
protected function createFixtures(array $fixtures)
{
$class = isset($this->_fixtureAliases[$name]) ? $this->_fixtureAliases[$name] : $name;
return isset($this->_fixtures[$class]) ? $this->_fixtures[$class] : null;
// normalize fixture configurations
$config = []; // configuration provided in test case
$aliases = []; // class name => alias or class name
foreach ($fixtures as $name => $fixture) {
if (!is_array($fixture)) {
$class = ltrim($fixture, '\\');
$fixtures[$name] = ['class' => $class];
$aliases[$class] = is_integer($name) ? $class : $name;
} elseif (isset($fixture['class'])) {
$class = ltrim($fixture['class'], '\\');
$config[$class] = $fixture;
$aliases[$class] = $name;
} else {
throw new InvalidConfigException("You must specify 'class' for the fixture '$name'.");
}
}
// create fixture instances
$instances = [];
$stack = array_reverse($fixtures);
while (($fixture = array_pop($stack)) !== null) {
if ($fixture instanceof Fixture) {
$class = get_class($fixture);
$name = isset($aliases[$class]) ? $aliases[$class] : $class;
unset($instances[$name]); // unset so that the fixture is added to the last in the next line
$instances[$name] = $fixture;
} else {
$class = ltrim($fixture['class'], '\\');
$name = isset($aliases[$class]) ? $aliases[$class] : $class;
if (!isset($instances[$name])) {
$instances[$name] = false;
$stack[] = $fixture = Yii::createObject($fixture);
foreach ($fixture->depends as $dep) {
// need to use the configuration provided in test case
$stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep];
}
} elseif ($instances[$name] === false) {
throw new InvalidConfigException("A circular dependency is detected for fixture '$class'.");
}
}
}
return $instances;
}
}
......@@ -46,9 +46,7 @@ class InitDbFixture extends DbFixture
*/
public function beforeLoad()
{
foreach ($this->schemas as $schema) {
$this->checkIntegrity(false, $schema);
}
$this->checkIntegrity(false);
}
/**
......@@ -56,9 +54,7 @@ class InitDbFixture extends DbFixture
*/
public function afterLoad()
{
foreach ($this->schemas as $schema) {
$this->checkIntegrity(true, $schema);
}
$this->checkIntegrity(true);
}
/**
......@@ -73,6 +69,22 @@ class InitDbFixture extends DbFixture
}
/**
* @inheritdoc
*/
public function beforeUnload()
{
$this->checkIntegrity(false);
}
/**
* @inheritdoc
*/
public function afterUnload()
{
$this->checkIntegrity(true);
}
/**
* Toggles the DB integrity check.
* @param boolean $check whether to turn on or off the integrity check.
*/
......
......@@ -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
*
* @param string $sizeStr $sizeStr
......
......@@ -616,6 +616,8 @@ class ActiveField extends Component
return [];
}
$options = [];
$enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
if ($enableClientValidation) {
$validators = [];
......
......@@ -25,22 +25,22 @@ class MyDbTestCase
public function setUp()
{
$this->unloadFixtures();
$this->loadFixtures();
}
public function tearDown()
{
$this->unloadFixtures();
}
protected function fixtures()
public function fixtures()
{
return [
'customers' => CustomerFixture::className(),
];
}
protected function globalFixtures()
public function globalFixtures()
{
return [
InitDbFixture::className(),
......@@ -71,24 +71,26 @@ class ActiveFixtureTest extends DatabaseTestCase
{
$test = new MyDbTestCase();
$test->setUp();
$fixture = $test->customers;
$fixture = $test->getFixture('customers');
$this->assertEquals(CustomerFixture::className(), get_class($fixture));
$this->assertEquals(2, count($fixture));
$this->assertEquals(1, $fixture['customer1']['id']);
$this->assertEquals('customer1@example.com', $fixture['customer1']['email']);
$this->assertEquals(2, $fixture['customer2']['id']);
$this->assertEquals('customer2@example.com', $fixture['customer2']['email']);
$test->tearDown();
}
public function testGetModel()
{
$test = new MyDbTestCase();
$test->setUp();
$fixture = $test->customers;
$fixture = $test->getFixture('customers');
$this->assertEquals(Customer::className(), get_class($fixture->getModel('customer1')));
$this->assertEquals(1, $fixture->getModel('customer1')->id);
$this->assertEquals('customer1@example.com', $fixture->getModel('customer1')->email);
$this->assertEquals(2, $fixture->getModel('customer2')->id);
$this->assertEquals('customer2@example.com', $fixture->getModel('customer2')->email);
$test->tearDown();
}
}
......@@ -78,7 +78,7 @@ class MyTestCase
return $this->getFixture($name);
}
protected function fixtures()
public function fixtures()
{
switch ($this->scenario) {
case 0: return [];
......
......@@ -172,7 +172,17 @@ class FileValidatorTest extends TestCase
$val->validateAttribute($m, 'attr_files_empty');
$this->assertTrue($m->hasErrors('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();
// too big
$val = new FileValidator(['maxSize' => 128]);
$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