Merge branch 'master' into developeruz-15726-exist-validator-useMaster

This commit is contained in:
SilverFire - Dmitry Naumenko
2018-02-23 23:42:15 +02:00
24 changed files with 318 additions and 52 deletions

View File

@ -342,7 +342,7 @@ to representative class names:
And so on.
Using the object format makes it possible to create your own conditions or to change the way default ones are built.
See [Creating Custom Conditions and Expressions](#creating-custom-conditions-and-expressions) chapter to learn more.
See [Adding Custom Conditions and Expressions](#adding-custom-conditions-and-expressions) chapter to learn more.
#### Appending Conditions <span id="appending-conditions"></span>

View File

@ -142,7 +142,7 @@ to the application configuration:
> Info: We use an sqlite database here for simplicity. Please refer to the [Database guide](db-dao.md) for more options.
Next we create a [database migration](db-migrations.md) to create a posts table.
Next we create a [database migration](db-migrations.md) to create a post table.
Make sure you have a separate configuration file as explained above, we need it to run the console commands below.
Running the following commands will
create a database migration file and apply the migration to the database:
@ -163,12 +163,12 @@ class Post extends ActiveRecord
{
public static function tableName()
{
return '{{posts}}';
return '{{post}}';
}
}
```
> Info: The model created here is an ActiveRecord class, which represents the data from the `posts` table.
> Info: The model created here is an ActiveRecord class, which represents the data from the `post` table.
> Please refer to the [active record guide](db-active-record.md) for more information.
To serve posts on our API, add the `PostController` in `controllers`:

View File

@ -1,12 +1,21 @@
Yii Framework 2 Change Log
==========================
2.0.15 under development
------------------------
2.0.14.1 under development
--------------------------
- Bug #15728, #15731: Fixed BC break in `Query::select()` method (silverfire)
- Bug #15692: Fix ExistValidator with targetRelation ignores filter (developeruz)
- Bug #15693: Fixed `yii\filters\auth\HttpHeaderAuth` to work correctly when pattern is set but was not matched (bboure)
- Bug #15696: Fix magic getter for ActiveRecord (developeruz)
- Bug #15726: Fix ExistValidator is broken for NOSQL (developeruz)
- Enh #15716: Implemented `\Traversable` in `yii\db\ArrayExpression` (silverfire)
- Bug #15678: Fixed `resetForm()` method in `yii.activeForm.js` which used an undefined variable (Izumi-kun)
- Bug #15742: Updated `yii\helpers\BaseHtml::setActivePlaceholder()` to be consistent with `activeLabel()` (edwards-sj)
- Bug #15692: Fix `yii\validators\ExistValidator` to respect filter when `targetRelation` is used (developeruz)
- Bug #15696: Fix magic getter for `yii\db\ActiveRecord` (developeruz)
- Bug #15707: Fixed JSON retrieving from MySQL (silverfire)
- Bug #15724: Changed shortcut in `yii\console\controllers\BaseMigrateController` for `comment` option from `-c` to `-C` due to conflict (Izumi-kun)
2.0.14 February 18, 2018

View File

@ -444,7 +444,7 @@
this.value = getValue($form, this);
this.status = 0;
var $container = $form.find(this.container),
$input = findInput($form, attribute),
$input = findInput($form, this),
$errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
$errorElement.removeClass(

View File

@ -160,7 +160,7 @@ class MigrateController extends BaseMigrateController
public function optionAliases()
{
return array_merge(parent::optionAliases(), [
'c' => 'comment',
'C' => 'comment',
'f' => 'fields',
'p' => 'migrationPath',
't' => 'migrationTable',

View File

@ -7,6 +7,9 @@
namespace yii\db;
use Traversable;
use yii\base\InvalidConfigException;
/**
* Class ArrayExpression represents an array SQL expression.
*
@ -22,7 +25,7 @@ namespace yii\db;
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
* @since 2.0.14
*/
class ArrayExpression implements ExpressionInterface, \ArrayAccess, \Countable
class ArrayExpression implements ExpressionInterface, \ArrayAccess, \Countable, \IteratorAggregate
{
/**
* @var null|string the type of the array elements. Defaults to `null` which means the type is
@ -33,8 +36,8 @@ class ArrayExpression implements ExpressionInterface, \ArrayAccess, \Countable
*/
private $type;
/**
* @var array|QueryInterface|mixed the array content. Either represented as an array of values or a [[Query]] that
* returns these values. A single value will be considered as an array containing one element.
* @var array|QueryInterface the array's content.
* In can be represented as an array of values or a [[Query]] that returns these values.
*/
private $value;
/**
@ -167,4 +170,21 @@ class ArrayExpression implements ExpressionInterface, \ArrayAccess, \Countable
{
return count($this->value);
}
/**
* Retrieve an external iterator
*
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator()
{
if ($this->getValue() instanceof QueryInterface) {
throw new InvalidConfigException('The ArrayExpression class can not be iterated when the value is a QueryInterface object');
}
return new \ArrayIterator($this->getValue());
}
}

View File

@ -658,22 +658,28 @@ PATTERN;
*/
protected function getUniqueColumns($columns)
{
$columns = array_unique($columns);
$unaliasedColumns = $this->getUnaliasedColumnsFromSelect();
$result = [];
foreach ($columns as $columnAlias => $columnDefinition) {
if ($columnDefinition instanceof Query) {
if (!$columnDefinition instanceof Query) {
if (is_string($columnAlias)) {
$existsInSelect = isset($this->select[$columnAlias]) && $this->select[$columnAlias] === $columnDefinition;
if ($existsInSelect) {
continue;
}
} elseif (is_integer($columnAlias)) {
$existsInSelect = in_array($columnDefinition, $unaliasedColumns, true);
$existsInResultSet = in_array($columnDefinition, $result, true);
if ($existsInSelect || $existsInResultSet) {
continue;
}
}
}
if (
(is_string($columnAlias) && isset($this->select[$columnAlias]) && $this->select[$columnAlias] === $columnDefinition)
|| (is_integer($columnAlias) && in_array($columnDefinition, $unaliasedColumns))
) {
unset($columns[$columnAlias]);
$result[$columnAlias] = $columnDefinition;
}
}
return $columns;
return $result;
}
/**

View File

@ -624,15 +624,15 @@ class QueryBuilder extends \yii\base\BaseObject
$columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
$sets = [];
foreach ($columns as $name => $value) {
$value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
if ($value instanceof ExpressionInterface) {
$sets[] = $this->db->quoteColumnName($name) . '=' . $this->buildExpression($value, $params);
$placeholder = $this->buildExpression($value, $params);
} else {
$phName = $this->bindParam(
isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value,
$params
);
$sets[] = $this->db->quoteColumnName($name) . '=' . $phName;
$placeholder = $this->bindParam($value, $params);
}
$sets[] = $this->db->quoteColumnName($name) . '=' . $placeholder;
}
return [$sets, $params];
}

View File

@ -0,0 +1,52 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\mysql;
use yii\db\ExpressionInterface;
use yii\db\JsonExpression;
/**
* Class ColumnSchema
*
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
* @since 2.0.14.1
*/
class ColumnSchema extends \yii\db\ColumnSchema
{
/**
* {@inheritdoc}
*/
public function dbTypecast($value)
{
if ($value instanceof ExpressionInterface) {
return $value;
}
if ($this->dbType === Schema::TYPE_JSON) {
return new JsonExpression($value, $this->type);
}
return $this->typecast($value);
}
/**
* {@inheritdoc}
*/
public function phpTypecast($value)
{
if ($value === null) {
return null;
}
if ($this->type === Schema::TYPE_JSON) {
return json_decode($value, true);
}
return parent::phpTypecast($value);
}
}

View File

@ -30,6 +30,11 @@ class Schema extends \yii\db\Schema implements ConstraintFinderInterface
{
use ConstraintFinderTrait;
/**
* {@inheritdoc}
*/
public $columnSchemaClass = 'yii\db\mysql\ColumnSchema';
/**
* @var bool whether MySQL used is older than 5.1.
*/

View File

@ -51,9 +51,14 @@ class HttpHeaderAuth extends AuthMethod
$authHeader = $request->getHeaders()->get($this->header);
if ($authHeader !== null) {
if ($this->pattern !== null && preg_match($this->pattern, $authHeader, $matches)) {
if ($this->pattern !== null) {
if (preg_match($this->pattern, $authHeader, $matches)) {
$authHeader = $matches[1];
} else {
return null;
}
}
$identity = $user->loginByAccessToken($authHeader, get_class($this));
if ($identity === null) {
$this->challenge($response);

View File

@ -1386,6 +1386,7 @@ class BaseHtml
protected static function setActivePlaceholder($model, $attribute, &$options = [])
{
if (isset($options['placeholder']) && $options['placeholder'] === true) {
$attribute = static::getAttributeName($attribute);
$options['placeholder'] = $model->getAttributeLabel($attribute);
}
}

View File

@ -35,7 +35,7 @@ return [
'Missing required arguments: {params}' => 'Nedostaju obavezni argumenti: {params}',
'Missing required parameters: {params}' => 'Nedostaju obavezni parametri: {params}',
'No' => 'Ne',
'No results found.' => 'Nema rezulatata.',
'No results found.' => 'Nema rezultata.',
'Only files with these MIME types are allowed: {mimeTypes}.' => 'Samo datoteke sa sljedećim MIME tipovima su dozvoljeni: {mimeTypes}.',
'Only files with these extensions are allowed: {extensions}.' => 'Samo datoteke sa sljedećim ekstenzijama su dozvoljeni: {extensions}.',
'Page not found.' => 'Stranica nije pronađena.',

View File

@ -12,6 +12,7 @@ namespace yiiunit\data\ar;
* @property string $title
* @property string $content
* @property int $version
* @property array $properties
*/
class Document extends ActiveRecord
{

23
tests/data/ar/Storage.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\data\ar;
/**
* Class Animal
*
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
* @property int $id
* @property array $data
*/
class Storage extends ActiveRecord
{
public static function tableName()
{
return 'storage';
}
}

View File

@ -23,6 +23,7 @@ DROP TABLE IF EXISTS `comment` CASCADE;
DROP TABLE IF EXISTS `dossier`;
DROP TABLE IF EXISTS `employee`;
DROP TABLE IF EXISTS `department`;
DROP TABLE IF EXISTS `storage`;
DROP VIEW IF EXISTS `animal_view`;
DROP TABLE IF EXISTS `T_constraints_4` CASCADE;
DROP TABLE IF EXISTS `T_constraints_3` CASCADE;
@ -202,6 +203,12 @@ CREATE TABLE `dossier` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `storage` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`data` JSON NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE VIEW `animal_view` AS SELECT * FROM `animal`;
INSERT INTO `animal` (`type`) VALUES ('yiiunit\data\ar\Cat');

View File

@ -177,7 +177,7 @@ CREATE TABLE "default_pk" (
CREATE TABLE "document" (
id serial primary key,
title varchar(255) not null,
content text not null,
content text,
version integer not null default 0
);
@ -325,6 +325,7 @@ INSERT INTO "bit_values" (id, val) VALUES (1, '0'), (2, '1');
DROP TABLE IF EXISTS "array_and_json_types" CASCADE;
CREATE TABLE "array_and_json_types" (
id SERIAL NOT NULL PRIMARY KEY,
intarray_col INT[],
textarray2_col TEXT[][],
json_col JSON,

View File

@ -62,6 +62,37 @@ abstract class QueryTest extends DatabaseTestCase
$query = new Query();
$query->select('name, name, name as X, name as X');
$this->assertEquals(['name', 'name as X'], array_values($query->select));
/** @see https://github.com/yiisoft/yii2/issues/15676 */
$query = (new Query())->select('id');
$this->assertSame(['id'], $query->select);
$query->select(['id', 'brand_id']);
$this->assertSame(['id', 'brand_id'], $query->select);
/** @see https://github.com/yiisoft/yii2/issues/15676 */
$query = (new Query())->select(['prefix' => 'LEFT(name, 7)', 'prefix_key' => 'LEFT(name, 7)']);
$this->assertSame(['prefix' => 'LEFT(name, 7)', 'prefix_key' => 'LEFT(name, 7)'], $query->select);
$query->addSelect(['LEFT(name,7) as test']);
$this->assertSame(['prefix' => 'LEFT(name, 7)', 'prefix_key' => 'LEFT(name, 7)', 'LEFT(name,7) as test'], $query->select);
$query->addSelect(['LEFT(name,7) as test']);
$this->assertSame(['prefix' => 'LEFT(name, 7)', 'prefix_key' => 'LEFT(name, 7)', 'LEFT(name,7) as test'], $query->select);
$query->addSelect(['test' => 'LEFT(name,7)']);
$this->assertSame(['prefix' => 'LEFT(name, 7)', 'prefix_key' => 'LEFT(name, 7)', 'LEFT(name,7) as test', 'test' => 'LEFT(name,7)'], $query->select);
/** @see https://github.com/yiisoft/yii2/issues/15731 */
$selectedCols = [
'total_sum' => 'SUM(f.amount)',
'in_sum' => 'SUM(IF(f.type = :type_in, f.amount, 0))',
'out_sum' => 'SUM(IF(f.type = :type_out, f.amount, 0))',
];
$query = (new Query())->select($selectedCols)->addParams([
':type_in' => 'in',
':type_out' => 'out',
':type_partner' => 'partner',
]);
$this->assertSame($selectedCols, $query->select);
$query->select($selectedCols);
$this->assertSame($selectedCols, $query->select);
}
public function testFrom()
@ -675,18 +706,4 @@ abstract class QueryTest extends DatabaseTestCase
$this->assertEquals('user11', $query->cache()->where(['id' => 1])->scalar($db));
}, 10);
}
/**
* @see https://github.com/yiisoft/yii2/issues/15676
*/
public function testIssue15676()
{
$query = (new Query())
->select('id')
->from('place');
$this->assertSame(['id'], $query->select);
$query->select(['id', 'brand_id']);
$this->assertSame(['id', 'brand_id'], $query->select);
}
}

View File

@ -7,6 +7,8 @@
namespace yiiunit\framework\db\mysql;
use yiiunit\data\ar\Storage;
/**
* @group db
* @group mysql
@ -14,4 +16,35 @@ namespace yiiunit\framework\db\mysql;
class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
{
public $driverName = 'mysql';
public function testJsonColumn()
{
if (version_compare($this->getConnection()->getSchema()->getServerVersion(), '5.7', '<')) {
$this->markTestSkipped('JSON columns are not supported in MySQL < 5.7');
}
if (version_compare(PHP_VERSION, '5.6', '<')) {
$this->markTestSkipped('JSON columns are not supported in PDO for PHP < 5.6');
}
$data = [
'obj' => ['a' => ['b' => ['c' => 2.7418]]],
'array' => [1,2,null,3],
'null_field' => null,
'boolean_field' => true,
'last_update_time' => '2018-02-21',
];
$storage = new Storage(['data' => $data]);
$this->assertTrue($storage->save(), 'Storage can be saved');
$this->assertNotNull($storage->id);
$retrievedStorage = Storage::findOne($storage->id);
$this->assertSame($data, $retrievedStorage->data, 'Properties are restored from JSON to array without changes');
$retrievedStorage->data = ['updatedData' => $data];
$this->assertSame(1, $retrievedStorage->update(), 'Storage can be updated');
$retrievedStorage->refresh();
$this->assertSame(['updatedData' => $data], $retrievedStorage->data, 'Properties have been changed during update');
}
}

View File

@ -195,21 +195,35 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
$value = $type->$attribute;
$this->assertEquals($expected, $value, 'In column ' . $attribute);
if ($value instanceof ArrayExpression) {
$this->assertInstanceOf('\ArrayAccess', $value);
$this->assertInstanceOf('\Traversable', $value);
foreach ($type->$attribute as $key => $v) { // testing arrayaccess
$this->assertSame($expected[$key], $value[$key]);
}
}
}
// Testing UPDATE
foreach ($attributes as $attribute => $expected) {
$type->markAttributeDirty($attribute);
}
$this->assertSame(1, $type->update(), 'The record got updated');
}
public function arrayValuesProvider()
{
return [
'simple arrays values' => [[
'intarray_col' => [new ArrayExpression([1,-2,null,'42'], 'int4', 1)],
'textarray2_col' => [new ArrayExpression([['text'], [null], [1]], 'text', 2)],
'intarray_col' => [
new ArrayExpression([1,-2,null,'42'], 'int4', 1),
new ArrayExpression([1,-2,null,42], 'int4', 1),
],
'textarray2_col' => [
new ArrayExpression([['text'], [null], [1]], 'text', 2),
new ArrayExpression([['text'], [null], ['1']], 'text', 2),
],
'json_col' => [['a' => 1, 'b' => null, 'c' => [1,3,5]]],
'jsonb_col' => [[null, 'a', 'b', '\"', '{"af"}']],
'jsonarray_col' => [new ArrayExpression([[',', 'null', true, 'false', 'f']], 'json')],
@ -217,11 +231,11 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
'arrays packed in classes' => [[
'intarray_col' => [
new ArrayExpression([1,-2,null,'42'], 'int', 1),
new ArrayExpression([1,-2,null,'42'], 'int4', 1),
new ArrayExpression([1,-2,null,42], 'int4', 1),
],
'textarray2_col' => [
new ArrayExpression([['text'], [null], [1]], 'text', 2),
new ArrayExpression([['text'], [null], [1]], 'text', 2),
new ArrayExpression([['text'], [null], ['1']], 'text', 2),
],
'json_col' => [
new JsonExpression(['a' => 1, 'b' => null, 'c' => [1,3,5]]),
@ -234,7 +248,6 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
'jsonarray_col' => [
new Expression("array['[\",\",\"null\",true,\"false\",\"f\"]'::json]::json[]"),
new ArrayExpression([[',', 'null', true, 'false', 'f']], 'json'),
]
]],
'scalars' => [[
@ -278,6 +291,7 @@ class UserAR extends ActiveRecord
/**
* {@inheritdoc}
* @property array id
* @property array intarray_col
* @property array textarray2_col
* @property array json_col

View File

@ -10,6 +10,7 @@ namespace yiiunit\framework\filters\auth;
use Yii;
use yii\filters\auth\AuthMethod;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\rest\Controller;
use yiiunit\framework\web\UserIdentity;
@ -26,6 +27,8 @@ class TestAuth extends AuthMethod
class TestController extends Controller
{
public $authMethods = [];
public function actionA()
{
return 'success';
@ -66,7 +69,7 @@ class TestController extends Controller
return [
'authenticator' => [
'class' => CompositeAuth::className(),
'authMethods' => [
'authMethods' => $this->authMethods ?: [
TestAuth::className(),
],
],
@ -123,4 +126,19 @@ class CompositeAuthTest extends \yiiunit\TestCase
$controller = Yii::$app->createController('test')[0];
$this->assertEquals('success', $controller->run('c'));
}
public function testCompositeAuth()
{
Yii::$app->request->headers->set('Authorization', base64_encode("foo:bar"));
/** @var TestAuthController $controller */
$controller = Yii::$app->createController('test')[0];
$controller->authMethods = [
HttpBearerAuth::className(),
TestAuth::className(),
];
try {
$this->assertEquals('success', $controller->run('b'));
} catch (UnauthorizedHttpException $e) {
}
}
}

View File

@ -1698,6 +1698,16 @@ HTML;
$this->assertContains('placeholder="Custom placeholder"', $html);
}
public function testActiveTextInput_placeholderFillFromModelTabular()
{
$model = new HtmlTestModel();
$html = Html::activeTextInput($model, '[0]name', ['placeholder' => true]);
$this->assertContains('placeholder="Name"', $html);
}
}
/**

View File

@ -80,6 +80,50 @@ describe('yii.activeForm', function () {
});
});
describe('resetForm method', function () {
var windowSetTimeoutStub;
beforeEach(function () {
windowSetTimeoutStub = sinon.stub(window, 'setTimeout', function (callback) {
callback();
});
});
afterEach(function () {
windowSetTimeoutStub.restore();
});
it('should remove classes from error element', function () {
var inputId = 'name';
var $input = $('#' + inputId);
var options = {
validatingCssClass: 'validating',
errorCssClass: 'error',
successCssClass: 'success',
validationStateOn: 'input'
};
$activeForm = $('#w0');
$activeForm.yiiActiveForm('destroy');
$activeForm.yiiActiveForm([
{
id: inputId,
input: '#' + inputId
}
], options);
$input.addClass(options.validatingCssClass);
$input.addClass(options.errorCssClass);
$input.addClass(options.successCssClass);
$input.addClass('test');
$activeForm.yiiActiveForm('resetForm');
assert.isFalse($input.hasClass(options.validatingCssClass));
assert.isFalse($input.hasClass(options.errorCssClass));
assert.isFalse($input.hasClass(options.successCssClass));
assert.isTrue($input.hasClass('test'));
});
});
describe('events', function () {
describe('afterValidateAttribute', function () {
var afterValidateAttributeSpy;