PHPackages                             dfware/ar-variation - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. dfware/ar-variation

ActiveYii2-extension[Localization &amp; i18n](/categories/localization)

dfware/ar-variation
===================

Provides support for ActiveRecord variation via related models in Yii2

1.0.0(2y ago)093BSD-3-ClausePHP

Since Sep 27Pushed 2y agoCompare

[ Source](https://github.com/dfware/ar-variation)[ Packagist](https://packagist.org/packages/dfware/ar-variation)[ GitHub Sponsors](https://github.com/klimov-paul)[ Patreon](https://www.patreon.com/klimov_paul)[ RSS](/packages/dfware-ar-variation/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (1)Dependencies (2)Versions (2)Used By (0)

Installation
------------

[](#installation)

The preferred way to install this extension is through [composer](http://getcomposer.org/download/).

Either run

```
composer require dfware/ar-variation

```

or add

```
"dfware/ar-variation": "*"
```

to the require section of your composer.json.

Usage
-----

[](#usage)

This extension provides support for ActiveRecord variation via related models. Variation means some particular entity have an attributes (fields), which values should vary depending on actual selected option. In database structure variation is implemented as many-to-many relation with extra columns at junction entity.

The most common example of such case is i18n feature: imagine we have an item, which title and description should be provided on several different languages. In relational database there will be 2 different tables for this case: one for the item and second - for the item translation, which have item id and language id along with actual title and description. A DDL for such solution will be following:

```
CREATE TABLE `Language`
(
   `id` varchar(5) NOT NULL,
   `name` varchar(64) NOT NULL,
   `locale` varchar(5) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE InnoDB;

CREATE TABLE `Item`
(
   `id` integer NOT NULL AUTO_INCREMENT,
   `name` varchar(64) NOT NULL,
   `price` integer,
    PRIMARY KEY (`id`)
) ENGINE InnoDB;

CREATE TABLE `ItemTranslation`
(
   `itemId` integer NOT NULL,
   `languageId` varchar(5) NOT NULL,
   `title` varchar(64) NOT NULL,
   `description` TEXT,
    PRIMARY KEY (`itemId`, `languageId`)
    FOREIGN KEY (`itemId`) REFERENCES `Item` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    FOREIGN KEY (`languageId`) REFERENCES `Language` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
) ENGINE InnoDB;
```

Usually in most cases there is no need for 'Item' to know about all its translations - it is enough to fetch only one, which is used as web application interface language.

This extension provides \[\[\\yii2tech\\ar\\variation\\VariationBehavior\]\] ActiveRecord behavior for such solution support in Yii2. You'll have to create an ActiveRecord class for 'Language', 'Item' and 'ItemTranslation' and attach \[\[\\yii2tech\\ar\\variation\\VariationBehavior\]\] in the following way:

```
class Item extends ActiveRecord
{
    public function behaviors()
    {
        return [
            'translations' => [
                'class' => VariationBehavior::className(),
                'variationsRelation' => 'translations',
                'defaultVariationRelation' => 'defaultTranslation',
                'variationOptionReferenceAttribute' => 'languageId',
                'optionModelClass' => Language::className(),
                'defaultVariationOptionReference' => function() {return Yii::$app->language;},
                'variationAttributeDefaultValueMap' => [
                    'title' => 'name'
                ],
            ],
        ];
    }

    public static function tableName()
    {
        return 'Item';
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getTranslations()
    {
        return $this->hasMany(ItemTranslation::className(), ['itemId' => 'id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getDefaultTranslation()
    {
        return $this->hasDefaultVariationRelation(); // convert "has many translations" into "has one defaultTranslation"
    }
}
```

Pay attention to the fact behavior is working through the 'has many' relation declared in the main ActiveRecord to the variation ActiveRecord. In the above example it will be relation 'translations'. You also have to declare default variation relation as 'has one', this can be easily done via \[\[\\yii2tech\\ar\\variation\\VariationBehavior::hasDefaultVariationRelation()\]\] method. Such relation inherits all information from the source one and applies extra condition on variation option reference, which is determined by \[\[\\yii2tech\\ar\\variation\\VariationBehavior::defaultVariationOptionReference\]\]. This reference should provide default value, which matches value of \[\[\\yii2tech\\ar\\variation\\VariationBehavior::variationOptionReferenceAttribute\]\] of the variation entity.

Accessing variation attributes
-------------------------------

[](#accessing-variation-attributes-)

Having `defaultVariationRelation` is important for the usage of the variation attributes. Being applied \[\[\\yii2tech\\ar\\variation\\VariationBehavior\]\] allows access to the variation fields as they were the main one:

```
$item = Item::findOne(1);
echo $item->title; // equal to `$item->defaultTranslation->title`
echo $item->description; // equal to `$item->defaultTranslation->description`
```

If it could be the main entity don't have a variation for particular option, you can use \[\[\\yii2tech\\ar\\variation\\VariationBehavior::$variationAttributeDefaultValueMap\]\] to provide the default value for the variation fields as it was done for 'title' in the above example:

```
$item = new Item(); // of course there is no translation for the new item
$item->name = 'new item';
echo $item->title; // outputs 'new item'
```

Querying variations
--------------------

[](#querying-variations-)

As it has been already said \[\[\\yii2tech\\ar\\variation\\VariationBehavior\]\] works through relations. Thus, in order to make variation attributes feature work, it will perform an extra query to retrieve the default variation model, which may produce performance impact in case you are working with several models. In order to reduce number of queries you may use `with()` on the default variation relation:

```
$items = Item::find()->with('defaultTranslation')->all(); // only 2 queries will be performed
foreach ($items as $item) {
    echo $item->title . '';
}
```

You may as well use main variations relation in `with()`. In this case default variation will be fetched from it without extra query:

```
$items = Item::find()->with('translations')->all(); // only 2 queries will be performed
foreach ($items as $item) {
    echo $item->title . ''; // no extra query
    var_dump($item->defaultTranslation);  // no extra query, `defaultTranslation` is populated from `translations`
}
```

If you are using relational database you can also use \[\[\\yii\\db\\ActiveQuery::joinWith()\]\]:

```
$items = Item::find()->joinWith('defaultTranslation')->all();
```

You may apply 'with' for the variation relation as default scope for the main ActiveRecord query:

```
class Item extends ActiveRecord
{
    // ...

    public static function find()
    {
        return parent::find()->with('defaultTranslation');
    }
}
```

Access particular variation
----------------------------

[](#access-particular-variation-)

You can always access default variation model via `getDefaultVariationModel()` method:

```
$item = Item::findOne(1);
$variationModel = $item->getDefaultVariationModel(); // get default variation instance
echo $item->defaultVariationModel->title; // default variation is also available as virtual property
```

However, in some cases there is a need of accessing particular variation, but not default one. This can be done via `getVariationModel()` method:

```
$item = Item::findOne(1);
$frenchTranslation = $item->getVariationModel('fr');
$russianTranslation = $item->getVariationModel('ru');
```

> Note: method `getVariationModel()` will load \[\[\\yii2tech\\ar\\variation\\VariationBehavior::variationsRelation\]\] relation fully, which may reduce performance. You should always prefer usage of \[\[getDefaultVariationModel()\]\] method if possible. You may also use eager loading for `variationsRelation` with extra condition filtering the results in order to save performance.

Creating variation setup web interface
---------------------------------------

[](#creating-variation-setup-web-interface-)

Usage of \[\[\\yii2tech\\ar\\variation\\VariationBehavior\]\] simplifies management of variations and creating a web interface for their setup.

The web controller for variation management may look like following:

```
use yii\base\Model;
use yii\web\Controller;
use Yii;

class ItemController extends Controller
{
    public function actionCreate()
    {
        $model = new Item();

        $post = Yii::$app->request->post();
        if ($model->load($post) && Model::loadMultiple($model->getVariationModels(), $post) && $model->save()) {
            return $this->redirect(['index']);
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }
}
```

Note that variation models should be populated with data from request manually, but they will be validated and saved automatically - you don't need to do this manually. Automatic processing of variation models will be performed only, if they have been fetched before owner validation or saving triggered. Thus it will not affect pure owner validation or saving.

The form view file can be following:

```
