Merge branch 'master' into 13920-validation-marks-valid-field-as-invalid

This commit is contained in:
Alexander Makarov
2023-08-26 08:15:07 +04:00
committed by GitHub
5 changed files with 123 additions and 27 deletions

View File

@ -5,6 +5,7 @@ Yii Framework 2 Change Log
------------------------
- Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher)
- Bug #19911: Resolved inconsistency in `ActiveRecord::getAttributeLabel()` with regard of overriding in primary model labels for attributes of related model in favor of allowing such overriding for all levels of relation nesting (PowerGamer1)
- Bug #19872: Fixed the definition of dirty attributes in AR properties for a non-associative array in case of changing the order of elements (eegusakov)
- Bug #19899: Fixed `GridView` in some cases calling `Model::generateAttributeLabel()` to generate label values that are never used (PowerGamer1)
- Bug #9899: Fix caching a MSSQL query with BLOB data type (terabytesoftw)
@ -17,6 +18,7 @@ Yii Framework 2 Change Log
- Enh #19884: Added support Enums in Query Builder (sk1t0n)
- Bug #19908: Fix associative array cell content rendering in Table widget (rhertogh)
- Bug #19906: Fixed multiline strings in the `\yii\console\widgets\Table` widget (rhertogh)
- Bug #19924: Fix `yii\i18n\Formatter` to not throw error `Unknown named parameter` under PHP 8 (arollmann)
- Bug #19914: Fixed `ArrayHelper::keyExists()` and `::remove()` functions when the key is a float and the value is `null` (rhertogh)
- Enh #19920: Broadened the accepted type of `Cookie::$expire` from `int` to `int|string|\DateTimeInterface|null` (rhertogh)

View File

@ -100,6 +100,11 @@ Upgrade from Yii 2.0.45
2.0.45 behavior, [introduce your own method](https://github.com/yiisoft/yii2/pull/19495/files).
* `yii\log\FileTarget::$rotateByCopy` is now deprecated and setting it to `false` has no effect since rotating of
the files is done only by copy.
* `yii\validators\UniqueValidator` and `yii\validators\ExistValidator`, when used on multiple attributes, now only
generate an error on a single attribute. Previously, they would report a separate error on each attribute.
Old behavior can be achieved by setting `'skipOnError' => false`, but this might have undesired side effects with
additional validators on one of the target attributes.
See [issue #19407](https://github.com/yiisoft/yii2/issues/19407)
Upgrade from Yii 2.0.44
-----------------------

View File

@ -1610,40 +1610,46 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
/**
* Returns the text label for the specified attribute.
* If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
* The attribute may be specified in a dot format to retrieve the label from related model or allow this model to override the label defined in related model.
* For example, if the attribute is specified as 'relatedModel1.relatedModel2.attr' the function will return the first label definition it can find
* in the following order:
* - the label for 'relatedModel1.relatedModel2.attr' defined in [[attributeLabels()]] of this model;
* - the label for 'relatedModel2.attr' defined in related model represented by relation 'relatedModel1' of this model;
* - the label for 'attr' defined in related model represented by relation 'relatedModel2' of relation 'relatedModel1'.
* If no label definition was found then the value of $this->generateAttributeLabel('relatedModel1.relatedModel2.attr') will be returned.
* @param string $attribute the attribute name
* @return string the attribute label
* @see generateAttributeLabel()
* @see attributeLabels()
* @see generateAttributeLabel()
*/
public function getAttributeLabel($attribute)
{
$labels = $this->attributeLabels();
if (isset($labels[$attribute])) {
return $labels[$attribute];
} elseif (strpos($attribute, '.')) {
$attributeParts = explode('.', $attribute);
$neededAttribute = array_pop($attributeParts);
$relatedModel = $this;
foreach ($attributeParts as $relationName) {
if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
$relatedModel = $relatedModel->$relationName;
} else {
try {
$relation = $relatedModel->getRelation($relationName);
} catch (InvalidParamException $e) {
return $this->generateAttributeLabel($attribute);
}
/* @var $modelClass ActiveRecordInterface */
$modelClass = $relation->modelClass;
$relatedModel = $modelClass::instance();
}
$model = $this;
$modelAttribute = $attribute;
for (;;) {
$labels = $model->attributeLabels();
if (isset($labels[$modelAttribute])) {
return $labels[$modelAttribute];
}
$labels = $relatedModel->attributeLabels();
if (isset($labels[$neededAttribute])) {
return $labels[$neededAttribute];
$parts = explode('.', $modelAttribute, 2);
if (count($parts) < 2) {
break;
}
list ($relationName, $modelAttribute) = $parts;
if ($model->isRelationPopulated($relationName) && $model->$relationName instanceof self) {
$model = $model->$relationName;
} else {
try {
$relation = $model->getRelation($relationName);
} catch (InvalidArgumentException $e) {
break;
}
/* @var $modelClass ActiveRecordInterface */
$modelClass = $relation->modelClass;
$model = $modelClass::instance();
}
}

View File

@ -460,7 +460,7 @@ class Formatter extends Component
}
$method = 'as' . $format;
if ($this->hasMethod($method)) {
return call_user_func_array([$this, $method], $params);
return call_user_func_array([$this, $method], array_values($params));
}
throw new InvalidArgumentException("Unknown format type: $format");

View File

@ -2192,4 +2192,87 @@ abstract class ActiveRecordTest extends DatabaseTestCase
$this->assertNotNull($order->virtualCustomer);
}
public function labelTestModelProvider()
{
$data = [];
// Model 2 and 3 are represented by objects.
$model1 = new LabelTestModel1();
$model2 = new LabelTestModel2();
$model3 = new LabelTestModel3();
$model2->populateRelation('model3', $model3);
$model1->populateRelation('model2', $model2);
$data[] = [$model1];
// Model 2 and 3 are represented by arrays instead of objects.
$model1 = new LabelTestModel1();
$model2 = ['model3' => []];
$model1->populateRelation('model2', $model2);
$data[] = [$model1];
return $data;
}
/**
* @dataProvider labelTestModelProvider
* @param \yii\db\ActiveRecord $model
*/
public function testGetAttributeLabel($model)
{
$this->assertEquals('model3.attr1 from model2', $model->getAttributeLabel('model2.model3.attr1'));
$this->assertEquals('attr2 from model3', $model->getAttributeLabel('model2.model3.attr2'));
$this->assertEquals('model3.attr3 from model2', $model->getAttributeLabel('model2.model3.attr3'));
$attr = 'model2.doesNotExist.attr1';
$this->assertEquals($model->generateAttributeLabel($attr), $model->getAttributeLabel($attr));
}
}
class LabelTestModel1 extends \yii\db\ActiveRecord
{
public function attributes()
{
return [];
}
public function getModel2()
{
return $this->hasOne(LabelTestModel2::className(), []);
}
}
class LabelTestModel2 extends \yii\db\ActiveRecord
{
public function attributes()
{
return [];
}
public function getModel3()
{
return $this->hasOne(LabelTestModel3::className(), []);
}
public function attributeLabels()
{
return [
'model3.attr1' => 'model3.attr1 from model2', // Override label defined in model3.
'model3.attr3' => 'model3.attr3 from model2', // Define label not defined in model3.
];
}
}
class LabelTestModel3 extends \yii\db\ActiveRecord
{
public function attributes()
{
return ['attr1', 'attr2', 'attr3'];
}
public function attributeLabels()
{
return [
'attr1' => 'attr1 from model3',
'attr2' => 'attr2 from model3',
];
}
}