Yii 2.0: A universal model attribute for its synthetic representation

3 followers

It is convenient to use the same identification attribute, say info, in all of the active records of your application. It should be a virtual read-only attribute defined by a getter method, its label being the model name.

One can easily use the info attribute in breadcrumbs, detail views, grid views and other places. It will be $model->info instead of $model->id, $model->name or $model->name . ' ' . $model->lastname.

Example

Let us consider a hypothetical application treating states and cities having one-to-many relation.

Models

class State extends ActiveRecord
{
    // ...
 
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'name' => 'State name',
            // ...
            'info' => 'State',
        ];
    }
 
    public function getInfo()
    {
        return $this->name;
    }
}
 
class City extends ActiveRecord
{
    // ...
 
    public function getInfo()
    {
        return $this->name;
    }
 
    public function getState()
    {
        return $this->hasOne(State::className(), ['id' => 'state_id']);
    }
}

The info attribute can be used throughout the application.

Title and breadcrumbs

This is the fragment of the city update view:

$this->title = 'Update ' . $model->info;
$this->params['breadcrumbs'][] = ['label' => 'States', 'url' => ['state/index']];
$this->params['breadcrumbs'][] = ['label' => $model->state->info, 'url' => ['state/view', 'id' => $model->state_id]];
$this->params['breadcrumbs'][] = ['label' => $model->info, 'url' => ['view', 'id' => $model->id]];
$this->params['breadcrumbs'][] = 'Update';

The page title "Update Seattle" is more informative then "Update City: 123" generated by the standard Gii template.

Cancel button

A cancel button in the state or the city view:

<?= Html::a(
    'Delete',
    ['delete', 'id' => $model->id],
    [
        'class' => 'btn btn-danger',
        'data' => [
            'confirm' => "Deleting {$model->info}. Are you sure?",
            'method' => 'post',
        ],
    ]
) ?>

This button, when pressed, asks: "Deleting Seattle. Are you sure?" instead of the standard "Are you sure you want to delete this item?".

DetailView attribute

A hyperlink in the detail view:

<?= DetailView::widget([
    'model' => $model,
    'attributes' => [
        // ...
        [
            'attribute' => 'state_id',
            'format' => 'raw',
            'value' => Html::a($model->state->info, ['state/view', 'id' => $model->state_id]),
        ],
    ],
]) ?>

GridView column

A column in a cities grid view:

<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        // ...
        'state.info',
    ],
]); ?>

In this case the 'state.info' column label will be 'State', according to the City model definition.

The dropDownList

In the drop down list of the city's form:

<?= $form->field($model, 'state_id')
    ->dropDownList(
        ArrayHelper::map(State::find()->all(), 'id', 'info'),
        ['prompt' => 'Select one']
    )
?>

General considerations

Universality

The universal info attribute simplifies the application design because the developer should not remember the attribute names for every model used. Also, the info attribute label is always the model name, so the 'father.grandfather.info' column specification in the detail or grid views do not require the implicit label specification, like 'father.grandfather.info:text:My label'.

The info attribute is similar to the __toString() method used to convert any PHP object to the string.

The info attribute does not guarantee the unambiguous identification string, but gives a rapid information on the model in question.

Custom ActiveRecord class

If most of your models have the name attribute or similar data useful for identification, it is helpful to define you own ActiveRecord class:

class ActiveRecord extends \yii\db\ActiveRecord
{
    // ...
 
    /**
     * @return string Instantiated model name
     */
    public function getCalledClassName()
    {
        $reflectionClass = new \ReflectionClass(get_called_class());
        return $reflectionClass->getShortName();
    }
 
    /**
     * @return string Short description of the model
     */
    public function getInfo()
    {
        // Take the model name
        $m = $this->getCalledClassName();
 
        // Compound info in different cases
        if ($this->isNewRecord)
            $i = 'New ' . $m;
        elseif ($this->hasAttribute ('name') and trim($this->name))
            $i = trim($this->name);
        // ... other cases specific to your application
        else
            $i = $m . ' ' . $this->id;
 
        // Return the result
        return $i;
    }
}

Override getInfo()

In some models you can override the default getInfo() method defined in your ActiveRecord class. For example, the active record of a person can be identified by the first and the last name:

class Person extends ActiveRecord
{
    // ...
 
    public function attributeLabels()
    {
        return [
            // ...
            'info' => 'Person',
        ];
    }
 
    public function getInfo()
    {
        // The first and the last names combined
        $i = trim(trim($this->name) . ' ' . trim($this->lastname)]);
 
        // If empty, take the default value
        if (! $i)
            $i = parent::getInfo();
 
        // Return the result
        return $i;
    }
}

The info value is never empty. Some possible examples of its values:

  1. Jane Doe
  2. Jane
  3. Doe
  4. Person 123
  5. New Person

The case 1 is when there are both the first and the last names evaluated. The cases 2 and 3 are when there is only the first or only the last name evaluated, respectively. The case 4 is when the first and the last names are both empty. The last case is for a new record not yet saved to the database.

Search ActiveRecord class

The info attribute should be defined as not searchable in the search class:

class PersonSearch extends Person
{
    // ...
    public function rules()
    {
        return [
            // ...
            [['!info'], 'safe', 'on' => '*']
        ];
    }
}

Gii templates

One can include the above code examples in the customized Gii model and CRUD templates.

Total 2 comments

#19930 report it
Alexandre Rodichevski at 2016/08/19 10:29am
Re: #19929

Hi EdoFre.

Thank you for your comment.

Imagine an application with a hundred of models, most of which having the name attribute. In this case the quickest way to write getInfo() method for every model is to create the custom ActiveRecord class like one I have shown. Other application's models simply extend this class, sometimes overriding the getInfo() method.

Alexandre

#19929 report it
EdoFre at 2016/08/19 10:08am
Interface

Might be smart to implement an interface for this behaviour? That way you're sure you have the getInfo() call implemented for every model.

Thanks for the good wiki entry.

Leave a comment

Please to leave your comment.

Write new article