Yii2 EAV
, (*1)
WIP, (*2)
Module for EAV (entity attribute value) anti pattern, (*3)
Installation
The preferred way to install this extension is through composer., (*4)
Either run, (*5)
php composer.phar require --prefer-dist nullref/yii2-eav "*"
or add, (*6)
"nullref/yii2-eav": "*"
to the require section of your composer.json
file., (*7)
Then You have run console command for install this module and run migrations:, (*8)
php yii module/install nullref/yii2-eav
Pay attention that if you don't use our application template
it needs to change config files structure to have ability run commands that show above., (*9)
Please check this documentation section, (*10)
Setup
Add behavior to target model, (*11)
use nullref\eav\behaviors\Entity;
use nullref\eav\models\attribute\Set;
use nullref\eav\models\Entity as EntityModel;
/**
* ...
* @property EntityModel $eav
* ...
*/
class Product extends \yii\db\ActiveRecord
//...
public function behaviors()
{
return [
/** ... **/
'eav' => [
'class' => Entity::class,
'entity' => function () {
return new EntityModel([
'sets' => [
Set::findOne(['code' => 'product']), //product -- set from db
],
]);
},
],
];
}
//...
}
Create set and attribute for it in admin panel, (*12)
Add attributes widget to entity edit form, (*13)
<?= \nullref\eav\widgets\Attributes::widget([
'form' => $form,
'model' => $model,
]) ?>
If you need some dynamic configuration sets of your model you can use method afterFind()
:, (*14)
public function afterFind()
{
$this->attachBehavior('eav', [
'class' => Entity::class,
'entity' => function () {
$setIds = $this->getCategories()->select('set_id')->column();
$setIds[] = Set::findOne(['code' => 'product'])->id;
return new EntityModel([
'sets' => Set::findAll(['id' => array_unique($setIds)]),
]);
},
]);
parent::afterFind();
}
In above example we have many-to-many relation product model with category which has set_id column., (*15)
Pay attention that this example could caused n+1 query problem. To prevent this problem use query caching or memoization.
For example, change:, (*16)
\nullref\eav\models\attribute\Set::findOne(['code' => 'product']),
to, (*17)
\nullref\useful\helpers\Memoize::call([Set::class, 'findOne'],[['code' => 'product']]),
Using in search model
If you need filtering your records by eav fields you need to modify YourModelSearch::search()
method by following code:, (*18)
//...
if (!$this->validate()) {
return $dataProvider;
}
//...
foreach ($this->eav->getAttributes() as $key => $value) {
$valueModel = $this->eav->getAttributeModel($key)->createValue();
$valueModel->setScenario('search');
$valueModel->load(['value' => $value], '');
if ($valueModel->validate(['value'])) {
$valueModel->addJoin($query, self::tableName());
$valueModel->addWhere($query);
}
}
//...
return $dataProvider;
To output columns in gridview use nullref\eav\helpers\Grid::getGridColumns()
:, (*19)
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => array_merge([
//...
'name',
], \nullref\eav\helpers\Grid::getGridColumns($searchModel), [
//...
[
'class' => 'yii\grid\ActionColumn',
],
]),
]); ?>
To configure which columns will be shown in grid go to attribute update page and select "Show on grid" checkbox., (*20)
Customization
To add custom types you need to use type \nullref\eav\components\TypesManager
.
To get more details please check \nullref\eav\Bootstrap::setupManager
as example of configuring base types., (*21)
You could call \nullref\eav\components\TypesManager::registerType
at bootstrap phase and define you own types of attributes., (*22)
Method registerType
takes one argument by type \nullref\eav\models\Type
this class contains all info about particular type:, (*23)
- name (unique string)
- label
- value model class (based on
\nullref\eav\widgets\AttributeInput
)
- form input class (based on
\nullref\eav\models\Value
)
TypesManager::get()->registerType(new Type(
Types::TYPE_IMAGE,
Yii::t('eav', 'Image'),
JsonValue::class,
ImageInput::class)
);
Filtering attributes
If you need filter EAV attributes you could use filterAttributes
and pass callable there:, (*24)
'eav' => [
'class' => Entity::class,
'entity' => function () {
return new EntityModel([
'sets' => [
Memoize::call([Set::class, 'findOne'], [['code' => 'product']]),
],
'filterAttributes' => function ($attributes) {
$fieldCheckerService = Yii::$container->get(CheckerService::class);
$result = [];
foreach ($attributes as $code => $attr) {
if ($fieldCheckerService->isAllowedForClass(self::class, $code)) {
$result[$code] = $attr;
}
}
return $result;
}
]);
},
],
Translations
And translations, (*25)