Skip to content

Commit

Permalink
Allow ActiveRecord::getAttributeLabel() to override labels of deeply …
Browse files Browse the repository at this point in the history
…nested relations.
  • Loading branch information
PowerGamer1 committed Jul 26, 2023
1 parent c8c0ea9 commit 89e1b01
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 26 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.49 under development
------------------------

- 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 #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)
- Bug #16208: Fix `yii\log\FileTarget` to not export empty messages (terabytesoftw)
Expand Down
57 changes: 31 additions & 26 deletions framework/db/BaseActiveRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -1610,40 +1610,45 @@ public static function isPrimaryKey($keys)

/**
* 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 (InvalidParamException $e) {
break;
}
/* @var $modelClass ActiveRecordInterface */
$modelClass = $relation->modelClass;
$model = $modelClass::instance();
}
}

Expand Down
74 changes: 74 additions & 0 deletions tests/framework/db/ActiveRecordTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2192,4 +2192,78 @@ public function testVirtualRelation()
$this->assertNotNull($order->virtualCustomer);
}

public function testGetAttributeLabel()
{
// 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);
$this->testGetAttributeLabelForModel($model1);

// Model 2 and 3 are represented by arrays instead of objects.
$model2 = ['model3' => []];
$model1->populateRelation('model2', $model2);
$this->testGetAttributeLabelForModel($model1);
}

private function testGetAttributeLabelForModel($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',
];
}
}

0 comments on commit 89e1b01

Please sign in to comment.