{"id":15169505,"url":"https://github.com/yii2tech/ar-variation","last_synced_at":"2025-10-01T02:31:25.616Z","repository":{"id":35495525,"uuid":"39765133","full_name":"yii2tech/ar-variation","owner":"yii2tech","description":"Variation behavior for ActiveRecord","archived":true,"fork":false,"pushed_at":"2019-07-30T11:12:32.000Z","size":65,"stargazers_count":48,"open_issues_count":0,"forks_count":10,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-09-22T22:01:58.796Z","etag":null,"topics":["activerecord","i18n","internationalization","many2many","variation-entity","yii","yii2","yii2-extension"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yii2tech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["klimov-paul"],"patreon":"klimov_paul"}},"created_at":"2015-07-27T09:05:48.000Z","updated_at":"2024-08-02T02:19:56.000Z","dependencies_parsed_at":"2022-09-13T02:51:54.611Z","dependency_job_id":null,"html_url":"https://github.com/yii2tech/ar-variation","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-variation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-variation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-variation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-variation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yii2tech","download_url":"https://codeload.github.com/yii2tech/ar-variation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219875269,"owners_count":16554660,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","i18n","internationalization","many2many","variation-entity","yii","yii2","yii2-extension"],"created_at":"2024-09-27T07:02:46.259Z","updated_at":"2025-10-01T02:31:20.364Z","avatar_url":"https://github.com/yii2tech.png","language":"PHP","funding_links":["https://github.com/sponsors/klimov-paul","https://patreon.com/klimov_paul"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/yii2tech\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://avatars2.githubusercontent.com/u/12951949\" height=\"100px\"\u003e\n    \u003c/a\u003e\n    \u003ch1 align=\"center\"\u003eActiveRecord Variation Extension for Yii 2\u003c/h1\u003e\n    \u003cbr\u003e\n\u003c/p\u003e\n\nThis extension provides support for ActiveRecord variation via related models.\nIn particular it allows implementing i18n feature for ActiveRecord.\n\nFor license information check the [LICENSE](LICENSE.md)-file.\n\n[![Latest Stable Version](https://poser.pugx.org/yii2tech/ar-variation/v/stable.png)](https://packagist.org/packages/yii2tech/ar-variation)\n[![Total Downloads](https://poser.pugx.org/yii2tech/ar-variation/downloads.png)](https://packagist.org/packages/yii2tech/ar-variation)\n[![Build Status](https://travis-ci.org/yii2tech/ar-variation.svg?branch=master)](https://travis-ci.org/yii2tech/ar-variation)\n\n\nInstallation\n------------\n\nThe preferred way to install this extension is through [composer](http://getcomposer.org/download/).\n\nEither run\n\n```\nphp composer.phar require --prefer-dist yii2tech/ar-variation\n```\n\nor add\n\n```json\n\"yii2tech/ar-variation\": \"*\"\n```\n\nto the require section of your composer.json.\n\n\nUsage\n-----\n\nThis extension provides support for ActiveRecord variation via related models.\nVariation means some particular entity have an attributes (fields), which values should vary depending on actual\nselected option. In database structure variation is implemented as many-to-many relation with extra columns at\njunction entity.\n\nThe most common example of such case is i18n feature: imagine we have an item, which title and description should\nbe provided on several different languages. In relational database there will be 2 different tables for this case:\none for the item and second - for the item translation, which have item id and language id along with actual title\nand description. A DDL for such solution will be following:\n\n```sql\nCREATE TABLE `Language`\n(\n   `id` varchar(5) NOT NULL,\n   `name` varchar(64) NOT NULL,\n   `locale` varchar(5) NOT NULL,\n    PRIMARY KEY (`id`)\n) ENGINE InnoDB;\n\nCREATE TABLE `Item`\n(\n   `id` integer NOT NULL AUTO_INCREMENT,\n   `name` varchar(64) NOT NULL,\n   `price` integer,\n    PRIMARY KEY (`id`)\n) ENGINE InnoDB;\n\nCREATE TABLE `ItemTranslation`\n(\n   `itemId` integer NOT NULL,\n   `languageId` varchar(5) NOT NULL,\n   `title` varchar(64) NOT NULL,\n   `description` TEXT,\n    PRIMARY KEY (`itemId`, `languageId`)\n    FOREIGN KEY (`itemId`) REFERENCES `Item` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n    FOREIGN KEY (`languageId`) REFERENCES `Language` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n) ENGINE InnoDB;\n```\n\nUsually in most cases there is no need for 'Item' to know about all its translations - it is enough to fetch\nonly one, which is used as web application interface language.\n\nThis extension provides [[\\yii2tech\\ar\\variation\\VariationBehavior]] ActiveRecord behavior for such solution\nsupport in Yii2. You'll have to create an ActiveRecord class for 'Language', 'Item' and 'ItemTranslation' and\nattach [[\\yii2tech\\ar\\variation\\VariationBehavior]] in the following way:\n\n```php\nclass Item extends ActiveRecord\n{\n    public function behaviors()\n    {\n        return [\n            'translations' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                'variationsRelation' =\u003e 'translations',\n                'defaultVariationRelation' =\u003e 'defaultTranslation',\n                'variationOptionReferenceAttribute' =\u003e 'languageId',\n                'optionModelClass' =\u003e Language::className(),\n                'defaultVariationOptionReference' =\u003e function() {return Yii::$app-\u003elanguage;},\n                'variationAttributeDefaultValueMap' =\u003e [\n                    'title' =\u003e 'name'\n                ],\n            ],\n        ];\n    }\n\n    public static function tableName()\n    {\n        return 'Item';\n    }\n\n    /**\n     * @return \\yii\\db\\ActiveQuery\n     */\n    public function getTranslations()\n    {\n        return $this-\u003ehasMany(ItemTranslation::className(), ['itemId' =\u003e 'id']);\n    }\n\n    /**\n     * @return \\yii\\db\\ActiveQuery\n     */\n    public function getDefaultTranslation()\n    {\n        return $this-\u003ehasDefaultVariationRelation(); // convert \"has many translations\" into \"has one defaultTranslation\"\n    }\n}\n```\n\nPay attention to the fact behavior is working through the 'has many' relation declared in the main ActiveRecord to\nthe variation ActiveRecord. In the above example it will be relation 'translations'. You also have to declare default\nvariation relation as 'has one', this can be easily done via [[\\yii2tech\\ar\\variation\\VariationBehavior::hasDefaultVariationRelation()]]\nmethod. Such relation inherits all information from the source one and applies extra condition on variation option reference,\nwhich is determined by [[\\yii2tech\\ar\\variation\\VariationBehavior::defaultVariationOptionReference]]. This reference should\nprovide default value, which matches value of [[\\yii2tech\\ar\\variation\\VariationBehavior::variationOptionReferenceAttribute]] of\nthe variation entity.\n\n\n## Accessing variation attributes \u003cspan id=\"accessing-variation-attributes\"\u003e\u003c/span\u003e\n\nHaving `defaultVariationRelation` is important for the usage of the variation attributes.\nBeing applied [[\\yii2tech\\ar\\variation\\VariationBehavior]] allows access to the variation fields as they were\nthe main one:\n\n```php\n$item = Item::findOne(1);\necho $item-\u003etitle; // equal to `$item-\u003edefaultTranslation-\u003etitle`\necho $item-\u003edescription; // equal to `$item-\u003edefaultTranslation-\u003edescription`\n```\n\nIf it could be the main entity don't have a variation for particular option, you can use [[\\yii2tech\\ar\\variation\\VariationBehavior::$variationAttributeDefaultValueMap]]\nto provide the default value for the variation fields as it was done for 'title' in the above example:\n\n```php\n$item = new Item(); // of course there is no translation for the new item\n$item-\u003ename = 'new item';\necho $item-\u003etitle; // outputs 'new item'\n```\n\n\n## Querying variations \u003cspan id=\"querying-variations\"\u003e\u003c/span\u003e\n\nAs it has been already said [[\\yii2tech\\ar\\variation\\VariationBehavior]] works through relations. Thus, in order to make\nvariation attributes feature work, it will perform an extra query to retrieve the default variation model, which may\nproduce performance impact in case you are working with several models.\nIn order to reduce number of queries you may use `with()` on the default variation relation:\n\n```php\n$items = Item::find()-\u003ewith('defaultTranslation')-\u003eall(); // only 2 queries will be performed\nforeach ($items as $item) {\n    echo $item-\u003etitle . '\u003cbr\u003e';\n}\n```\n\nYou may as well use main variations relation in `with()`. In this case default variation will be fetched from it without\nextra query:\n\n```php\n$items = Item::find()-\u003ewith('translations')-\u003eall(); // only 2 queries will be performed\nforeach ($items as $item) {\n    echo $item-\u003etitle . '\u003cbr\u003e'; // no extra query\n    var_dump($item-\u003edefaultTranslation);  // no extra query, `defaultTranslation` is populated from `translations`\n}\n```\n\nIf you are using relational database you can also use [[\\yii\\db\\ActiveQuery::joinWith()]]:\n\n```php\n$items = Item::find()-\u003ejoinWith('defaultTranslation')-\u003eall();\n```\n\nYou may apply 'with' for the variation relation as default scope for the main ActiveRecord query:\n\n```php\nclass Item extends ActiveRecord\n{\n    // ...\n\n    public static function find()\n    {\n        return parent::find()-\u003ewith('defaultTranslation');\n    }\n}\n```\n\n\n## Access particular variation \u003cspan id=\"access-particular-variation\"\u003e\u003c/span\u003e\n\nYou can always access default variation model via `getDefaultVariationModel()` method:\n\n```php\n$item = Item::findOne(1);\n$variationModel = $item-\u003egetDefaultVariationModel(); // get default variation instance\necho $item-\u003edefaultVariationModel-\u003etitle; // default variation is also available as virtual property\n```\n\nHowever, in some cases there is a need of accessing particular variation, but not default one.\nThis can be done via `getVariationModel()` method:\n\n```php\n$item = Item::findOne(1);\n$frenchTranslation = $item-\u003egetVariationModel('fr');\n$russianTranslation = $item-\u003egetVariationModel('ru');\n```\n\n\u003e Note: method `getVariationModel()` will load [[\\yii2tech\\ar\\variation\\VariationBehavior::variationsRelation]] relation\n  fully, which may reduce performance. You should always prefer usage of [[getDefaultVariationModel()]] method if possible.\n  You may also use eager loading for `variationsRelation` with extra condition filtering the results in order to save\n  performance.\n\n\n## Creating variation setup web interface \u003cspan id=\"creating-variation-setup-web-interface\"\u003e\u003c/span\u003e\n\nUsage of [[\\yii2tech\\ar\\variation\\VariationBehavior]] simplifies management of variations and creating a web interface\nfor their setup.\n\nThe web controller for variation management may look like following:\n\n```php\nuse yii\\base\\Model;\nuse yii\\web\\Controller;\nuse Yii;\n\nclass ItemController extends Controller\n{\n    public function actionCreate()\n    {\n        $model = new Item();\n\n        $post = Yii::$app-\u003erequest-\u003epost();\n        if ($model-\u003eload($post) \u0026\u0026 Model::loadMultiple($model-\u003egetVariationModels(), $post) \u0026\u0026 $model-\u003esave()) {\n            return $this-\u003eredirect(['index']);\n        }\n\n        return $this-\u003erender('create', [\n            'model' =\u003e $model,\n        ]);\n    }\n}\n```\n\nNote that variation models should be populated with data from request manually, but they will be validated and saved\nautomatically - you don't need to do this manually. Automatic processing of variation models will be performed only, if\nthey have been fetched before owner validation or saving triggered. Thus it will not affect pure owner validation or saving.\n\nThe form view file can be following:\n\n```php\n\u003c?php\nuse yii\\helpers\\ArrayHelper;\nuse yii\\helpers\\Html;\nuse yii\\widgets\\ActiveForm;\n\n/* @var $model Item */\n?\u003e\n\u003c?php $form = ActiveForm::begin(); ?\u003e\n\n\u003c?= $form-\u003efield($model, 'name'); ?\u003e\n\u003c?= $form-\u003efield($model, 'price'); ?\u003e\n\n\u003c?php foreach ($model-\u003egetVariationModels() as $index =\u003e $variationModel): ?\u003e\n    \u003c?= $form-\u003efield($variationModel, \"[{$index}]title\")-\u003elabel($variationModel-\u003egetAttributeLabel('title') . ' (' . $variationModel-\u003elanguageId . ')'); ?\u003e\n    \u003c?= $form-\u003efield($variationModel, \"[{$index}]description\")-\u003elabel($variationModel-\u003egetAttributeLabel('description') . ' (' . $variationModel-\u003elanguageId . ')'); ?\u003e\n\u003c?php endforeach; ?\u003e\n\n\u003cdiv class=\"form-group\"\u003e\n    \u003c?= Html::submitButton('Save', ['class' =\u003e 'btn btn-primary']) ?\u003e\n\u003c/div\u003e\n\n\u003c?php ActiveForm::end(); ?\u003e\n```\n\n\n## Saving default variation \u003cspan id=\"saving-default-variation\"\u003e\u003c/span\u003e\n\nIt is not necessary to process all possible variations at once - you can operate only single variation model, validating\nand saving it. For example: you can provide a web interface where user can setup only the translation for the current language.\nDoing so it is better to setup [[\\yii2tech\\ar\\variation\\VariationBehavior::$variationAttributeDefaultValueMap]] value, allowing\nmagic access to the variation attributes.\nBeing fetched default variation model will be validated and saved along with the main model:\n\n```php\n$item = Item::findOne($id);\n\n$item-\u003etitle = ''; // setup of `$item-\u003edefaultTranslation-\u003etitle`\nvar_dump($item-\u003evalidate()); // outputs: `false`\n\n$item-\u003etitle = 'new title';\n$item-\u003esave(); // invokes `$item-\u003edefaultTranslation-\u003esave()`\n```\n\nIn case attribute in mentioned at [[\\yii2tech\\ar\\variation\\VariationBehavior::$variationAttributeDefaultValueMap]], it\nwill be available for setting as well, even if default variation model does not exists: in such case it will be\ncreated automatically. For example:\n\n```php\n$item = new Item();\n$item-\u003ename = 'new name';\n$item-\u003etitle = 'translation title'; // setup of `$item-\u003edefaultTranslation` attribute, creating default variation model\n$item-\u003edescription = 'translation description';\n$item-\u003esave(); // saving both main model and default variation model\n```\n\nMarking variation attributes at the main model as 'safe' you can create a web interface, which sets up them in a simple way.\nModel code should look like following:\n\n```php\nclass Item extends ActiveRecord\n{\n    public function behaviors()\n    {\n        return [\n            'translations' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                // ...\n                'variationAttributeDefaultValueMap' =\u003e [\n                    'title' =\u003e 'name',\n                    'description' =\u003e null,\n                ],\n            ],\n        ];\n    }\n\n    public function rules()\n    {\n        return [\n            // ...\n            [['title', 'description'], 'safe'] // allow 'title' and 'description' to be populated via main model\n        ];\n    }\n\n    // ...\n}\n```\n\nInside the view you can use variation attributes at the main model directly:\n\n```php\n\u003c?php\nuse yii\\helpers\\ArrayHelper;\nuse yii\\helpers\\Html;\nuse yii\\widgets\\ActiveForm;\n\n/* @var $model Item */\n?\u003e\n\u003c?php $form = ActiveForm::begin(); ?\u003e\n\n\u003c?= $form-\u003efield($model, 'name'); ?\u003e\n\u003c?= $form-\u003efield($model, 'price'); ?\u003e\n\n\u003c?= $form-\u003efield($model, \"title\"); ?\u003e\n\u003c?= $form-\u003efield($model, \"description\")-\u003etextarea(); ?\u003e\n\n\u003cdiv class=\"form-group\"\u003e\n    \u003c?= Html::submitButton('Save', ['class' =\u003e 'btn btn-primary']) ?\u003e\n\u003c/div\u003e\n\n\u003c?php ActiveForm::end(); ?\u003e\n```\n\nThen the controller code will be simple:\n\n```php\nuse yii\\web\\Controller;\nuse Yii;\n\nclass ItemController extends Controller\n{\n    public function actionCreate()\n    {\n        $model = new Item();\n\n        if ($model-\u003eload(Yii::$app-\u003erequest-\u003epost()) \u0026\u0026 $model-\u003esave()) {\n            // variation attributes are populated automatically\n            // and variation model saved\n            return $this-\u003eredirect(['index']);\n        }\n\n        return $this-\u003erender('create', [\n            'model' =\u003e $model,\n        ]);\n    }\n}\n```\n\n\n## Additional variation conditions \u003cspan id=\"additional-variation-conditions\"\u003e\u003c/span\u003e\n\nThere are case, when variation options or variation entities have extra filtering conditions or attributes.\nFor example: assume we have a database of the developers with their payment rates, which varies per particular\nwork type. Work types are grouped by categories: 'front-end', 'back-end', 'database' etc. And payment rates should\nbe set for regular working time and for over-timing separately.\nThe DDL for such use case can be following:\n\n```sql\nCREATE TABLE `Developer`\n(\n   `id` integer NOT NULL AUTO_INCREMENT,\n   `name` varchar(64) NOT NULL,\n    PRIMARY KEY (`id`)\n) ENGINE InnoDB;\n\nCREATE TABLE `WorkTypeGroup`\n(\n   `id` integer NOT NULL AUTO_INCREMENT,\n   `name` varchar(64) NOT NULL,\n    PRIMARY KEY (`id`)\n) ENGINE InnoDB;\n\nCREATE TABLE `WorkType`\n(\n   `id` integer NOT NULL AUTO_INCREMENT,\n   `name` varchar(64) NOT NULL,\n   `groupId` integer NOT NULL,\n    PRIMARY KEY (`id`)\n    FOREIGN KEY (`groupId`) REFERENCES `WorkTypeGroup` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n) ENGINE InnoDB;\n\nCREATE TABLE `DeveloperPaymentRate`\n(\n   `developerId` integer NOT NULL,\n   `workTypeId` varchar(5) NOT NULL,\n   `paymentRate` integer NOT NULL,\n   `isOvertime` integer(1) NOT NULL,\n    PRIMARY KEY (`developerId`, `workTypeId`)\n    FOREIGN KEY (`developerId`) REFERENCES `Developer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n    FOREIGN KEY (`workTypeId`) REFERENCES `WorkType` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n) ENGINE InnoDB;\n```\n\nIn this case you may want to setup 'front-end' and 'back-end' separately (using different web interface or something).\nYou can apply an extra filtering condition for the 'option' Active Record query using [[\\yii2tech\\ar\\variation\\VariationBehavior::optionQueryFilter]]:\n\n```php\nclass Developer extends ActiveRecord\n{\n    public function behaviors()\n    {\n        return [\n            'frontEndPaymentRates' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                'variationsRelation' =\u003e 'paymentRates',\n                'variationOptionReferenceAttribute' =\u003e 'workTypeId',\n                'optionModelClass' =\u003e WorkType::className(),\n                'optionQueryFilter' =\u003e [\n                    'groupId' =\u003e WorkType::GROUP_FRONT_END // add 'where' condition to the `WorkType` query\n                ],\n            ],\n            'backEndPaymentRates' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                'variationsRelation' =\u003e 'paymentRates',\n                'variationOptionReferenceAttribute' =\u003e 'workTypeId',\n                'optionModelClass' =\u003e WorkType::className(),\n                // you can use a PHP callable as filter as well:\n                'optionQueryFilter' =\u003e function ($query) {\n                    $query-\u003eandWhere(['groupId' =\u003e WorkType::GROUP_BACK_END]);\n                }\n            ],\n        ];\n    }\n    // ...\n}\n```\n\nIn this case you'll have to access `getVariationModels()` from the behavior instance rather then the owner directly:\n\n```php\n$developer = new Developer();\n$developer-\u003egetBehavior('frontEndPaymentRates')-\u003egetVariationModels(); // get 'front-end' payment rates\n$developer-\u003egetBehavior('backEndPaymentRates')-\u003egetVariationModels(); // get 'back-end' payment rates\n```\n\nYou may as well separate variations using 'overtime' conditions: setup regular time and overtime payment rates in\ndifferent process. For such purpose you'll have to declare 2 separated relations for 'regular time' and 'overtime'\npayment rates:\n\n```php\nclass Developer extends ActiveRecord\n{\n    public function behaviors()\n    {\n        return [\n            'regularPaymentRates' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                'variationsRelation' =\u003e 'regularPaymentRates',\n                'variationOptionReferenceAttribute' =\u003e 'workTypeId',\n                'optionModelClass' =\u003e WorkType::className(),\n            ],\n            'overtimePaymentRates' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                'variationsRelation' =\u003e 'overtimePaymentRates',\n                'variationOptionReferenceAttribute' =\u003e 'workTypeId',\n                'optionModelClass' =\u003e WorkType::className(),\n            ],\n        ];\n    }\n\n    public function getPaymentRates()\n    {\n        return $this-\u003ehasMany(PaymentRates::className(), ['developerId' =\u003e 'id']); // basic 'payment rates' relation\n    }\n\n    public function getRegularPaymentRates()\n    {\n        return $this-\u003egetPaymentRates()-\u003eandWhere(['isOvertime' =\u003e false]); // regular payment rates\n    }\n\n    public function getOvertimePaymentRates()\n    {\n        return $this-\u003egetPaymentRates()-\u003eandWhere(['isOvertime' =\u003e true]); // overtime payment rates\n    }\n\n    // ...\n}\n```\n\nIn this case variation will be loaded only for particular rate type and saved with corresponding value of the `isOvertime`\nflag attribute. However, automatic detection of the extra variation model attributes will work only for 'hash' query conditions.\nIf you have a complex variation option filtering logic, you'll need to setup [[\\yii2tech\\ar\\variation\\VariationBehavior::variationModelDefaultAttributes]]\nmanually.\n\nIn the example above you may not want to save empty variation data in database: if particular developer have no particular\n'front-end' skill like 'AngularJS' he have no payment rate for it and thus there is no reason to save an empty 'PaymentRate'\nrecord for it.\nYou may use [[\\yii2tech\\ar\\variation\\VariationBehavior::variationSaveFilter]] to determine which variation record should\nbe saved or not. For example:\n\n```php\nclass Developer extends ActiveRecord\n{\n    public function behaviors()\n    {\n        return [\n            'paymentRates' =\u003e [\n                'class' =\u003e VariationBehavior::className(),\n                'variationsRelation' =\u003e 'regularPaymentRates',\n                'variationOptionReferenceAttribute' =\u003e 'workTypeId',\n                'optionModelClass' =\u003e WorkType::className(),\n                'variationSaveFilter' =\u003e function ($model) {\n                    return !empty($model-\u003epaymentRate);\n                },\n            ],\n        ];\n    }\n\n    // ...\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyii2tech%2Far-variation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyii2tech%2Far-variation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyii2tech%2Far-variation/lists"}