{"id":15169735,"url":"https://github.com/yii2tech/ar-role","last_synced_at":"2025-10-01T02:31:39.243Z","repository":{"id":57087029,"uuid":"41669191","full_name":"yii2tech/ar-role","owner":"yii2tech","description":"ActiveRecord behavior, which provides relation roles (table inheritance)","archived":true,"fork":false,"pushed_at":"2019-07-03T11:23:46.000Z","size":31,"stargazers_count":35,"open_issues_count":0,"forks_count":9,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-08-15T22:03:56.974Z","etag":null,"topics":["activerecord","role","roles","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-08-31T10:12:58.000Z","updated_at":"2024-11-26T06:31:21.000Z","dependencies_parsed_at":"2022-08-20T15:31:11.552Z","dependency_job_id":null,"html_url":"https://github.com/yii2tech/ar-role","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/yii2tech/ar-role","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-role","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-role/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-role/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-role/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yii2tech","download_url":"https://codeload.github.com/yii2tech/ar-role/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yii2tech%2Far-role/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276210451,"owners_count":25603724,"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","status":"online","status_checked_at":"2025-09-21T02:00:07.055Z","response_time":72,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","role","roles","yii","yii2","yii2-extension"],"created_at":"2024-09-27T07:21:47.900Z","updated_at":"2025-10-01T02:31:38.962Z","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 Role Inheritance Extension for Yii2\u003c/h1\u003e\n    \u003cbr\u003e\n\u003c/p\u003e\n\nThis extension provides support for ActiveRecord relation role (table inheritance) composition.\n\nFor license information check the [LICENSE](LICENSE.md)-file.\n\n[![Latest Stable Version](https://poser.pugx.org/yii2tech/ar-role/v/stable.png)](https://packagist.org/packages/yii2tech/ar-role)\n[![Total Downloads](https://poser.pugx.org/yii2tech/ar-role/downloads.png)](https://packagist.org/packages/yii2tech/ar-role)\n[![Build Status](https://travis-ci.org/yii2tech/ar-role.svg?branch=master)](https://travis-ci.org/yii2tech/ar-role)\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-role\n```\n\nor add\n\n```json\n\"yii2tech/ar-role\": \"*\"\n```\n\nto the require section of your composer.json.\n\n\nUsage\n-----\n\nThis extension provides support for ActiveRecord relation role composition, which is also known as table inheritance.\n\nFor example: assume we have a database for the University. There are students studying in the University and there are\ninstructors teaching the students. Student has a study group and scholarship information, while instructor has a rank\nand salary. However, both student and instructor have name, address, phone number and so on. Thus we can split\ntheir data in the three different tables:\n\n - 'Human' - stores common data\n - 'Student' - stores student special data and reference to the 'Human' record\n - 'Instructor' - stores instructor special data and reference to the 'Human' record\n\nDDL for such solution may look like following:\n\n```sql\nCREATE TABLE `Human`\n(\n   `id` integer NOT NULL AUTO_INCREMENT,\n   `role` varchar(20) NOT NULL,\n   `name` varchar(64) NOT NULL,\n   `address` varchar(64) NOT NULL,\n   `phone` varchar(20) NOT NULL,\n    PRIMARY KEY (`id`)\n) ENGINE InnoDB;\n\nCREATE TABLE `Student`\n(\n   `humanId` integer NOT NULL,\n   `studyGroupId` integer NOT NULL,\n   `hasScholarship` integer(1) NOT NULL,\n    PRIMARY KEY (`humanId`)\n    FOREIGN KEY (`humanId`) REFERENCES `Human` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n) ENGINE InnoDB;\n\nCREATE TABLE `Instructor`\n(\n   `humanId` integer NOT NULL,\n   `rankId` integer NOT NULL,\n   `salary` integer NOT NULL,\n    PRIMARY KEY (`humanId`)\n    FOREIGN KEY (`humanId`) REFERENCES `Human` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n) ENGINE InnoDB;\n```\n\nThis extension introduces [[\\yii2tech\\ar\\role\\RoleBehavior]] ActiveRecord behavior, which allows role relation based\nActiveRecord inheritance.\nIn oder to make it work, first of all, you should create an ActiveRecord class for the base table, in our example it\nwill be 'Human':\n\n```php\nclass Human extends \\yii\\db\\ActiveRecord\n{\n    public static function tableName()\n    {\n        return 'Human';\n    }\n}\n```\n\nThen you will be able to compose ActiveRecord classes, which implements role-based inheritance using [[\\yii2tech\\ar\\role\\RoleBehavior]].\nThere are 2 different ways for such classes composition:\n - Master role inheritance\n - Slave role inheritance\n\n\n## Master role inheritance \u003cspan id=\"master-role-inheritance\"\u003e\u003c/span\u003e\n\nThis approach assumes role ActiveRecord class be descendant of the base role class:\n\n```php\nclass Student extends Human // extending `Human` - not `ActiveRecord`!\n{\n    public function behaviors()\n    {\n        return [\n            'roleBehavior' =\u003e [\n                'class' =\u003e RoleBehavior::className(), // Attach role behavior\n                'roleRelation' =\u003e 'studentRole', // specify name of the relation to the slave table\n                'roleAttributes' =\u003e [\n                    'roleId' =\u003e Human::ROLE_STUDENT // mark 'Human' record as 'student'\n                ],\n            ],\n        ];\n    }\n\n    public function getStudentRole()\n    {\n        // Here `StudentRole` is and ActiveRecord, which uses 'Student' table :\n        return $this-\u003ehasOne(StudentRole::className(), ['humanId' =\u003e 'id']);\n    }\n}\n```\n\nThe main benefit of this approach is that role class directly inherits all methods, validation and other logic from\nthe base one. However, you'll need to declare an extra ActiveRecord class, which corresponds the role table.\nYet another problem is that you'll need to separate 'Student' records from 'Instructor' ones for the search process.\nWithout following code, it will return all 'Human' records, both 'Student' and 'Instructor':\n\n```php\n$students = Student::find()-\u003eall();\n```\n\nThe solution for this could be introduction of special column 'role' in the 'Human' table and usage of the default\nscope:\n\n```php\nclass Student extends Human\n{\n    // ...\n\n    public static function find()\n    {\n        return parent::find()-\u003ewhere(['role' =\u003e 'student']);\n    }\n}\n```\n\nThis approach should be chosen in case most functionality depends on the 'Human' attributes.\n\n\n## Slave role inheritance \u003cspan id=\"slave-role-inheritance\"\u003e\u003c/span\u003e\n\nThis approach assumes role ActiveRecord does not extends the base one, but relates to it:\n\n```php\nclass Instructor extends \\yii\\db\\ActiveRecord // do not extending `Human`!\n{\n    public function behaviors()\n    {\n        return [\n            'roleBehavior' =\u003e [\n                'class' =\u003e RoleBehavior::className(), // Attach role behavior\n                'roleRelation' =\u003e 'human', // specify name of the relation to the master table\n                'isOwnerSlave' =\u003e true, // indicate that owner is a role slave - not master\n                'roleAttributes' =\u003e [\n                    'roleId' =\u003e Human::ROLE_STUDENT // will be applied to the 'Human' record\n                ],\n            ],\n        ];\n    }\n\n    public function getHuman()\n    {\n        return $this-\u003ehasOne(Human::className(), ['id' =\u003e 'humanId']);\n    }\n}\n```\n\nThis approach does not require extra ActiveRecord class for functioning and it does not need default scope specification.\nIt does not directly inherit logic declared in the base ActiveRecord, however any custom method declared in the related\nclass will be available via magic method `__call()` mechanism. Thus if class `Human` has method `sayHello()`, you are\nable to invoke it through `Instructor` instance.\n\nThis approach should be chosen in case most functionality depends on the 'Instructor' attributes.\n\n\n## Accessing role attributes \u003cspan id=\"accessing-role-attributes\"\u003e\u003c/span\u003e\n\nAfter being attached [[\\yii2tech\\ar\\role\\RoleBehavior]] provides access to the properties of the model bound by relation,\nwhich is specified via [[\\yii2tech\\ar\\role\\RoleBehavior::roleRelation]], as they were the main one:\n\n```php\n$model = Student::findOne(1);\necho $model-\u003estudyGroupId; // equals to $model-\u003estudentRole-\u003estudyGroupId\n\n$model = Instructor::findOne(2);\necho $model-\u003ename; // equals to $model-\u003ehuman-\u003ename\n```\n\nIf the related model does not exist, for example, in case of new record, it will be automatically instantiated:\n\n```php\n$model = new Student();\n$model-\u003estudyGroupId = 12;\n\n$model = new Instructor();\n$model-\u003ename = 'John Doe';\n```\n\n\n## Accessing role methods \u003cspan id=\"accessing-role-methods\"\u003e\u003c/span\u003e\n\nAny non-static method declared in the model related via [[\\yii2tech\\ar\\role\\RoleBehavior::roleRelation]] can be accessed\nfrom the owner model:\n\n```php\nclass Human extends \\yii\\db\\ActiveRecord\n{\n    // ...\n\n    public function sayHello($name)\n    {\n        return 'Hello, ' . $name;\n    }\n}\n\nclass Instructor extends \\yii\\db\\ActiveRecord\n{\n    public function behaviors()\n    {\n        return [\n            'roleBehavior' =\u003e [\n                'class' =\u003e RoleBehavior::className(), // Attach role behavior\n                // ...\n            ],\n        ];\n    }\n}\n\n$model = new Instructor();\necho $model-\u003esayHello('John'); // outputs: 'Hello, John'\n```\n\nThis feature allows to inherit logic from the base role model in case of using 'slave' behavior setup approach.\nHowever, this works both for the 'master' and 'slave' role approaches.\n\n\n## Validation \u003cspan id=\"validation\"\u003e\u003c/span\u003e\n\nEach time the main model is validated the related role model will be validated as well and its errors will be attached\nto the main model:\n\n```php\n$model = new Student();\n$model-\u003estudyGroupId = 'invalid value';\nvar_dump($model-\u003evalidate()); // outputs \"false\"\nvar_dump($model-\u003ehasErrors('studyGroupId')); // outputs \"true\"\n```\n\nYou may as well specify validation rules for the related model attributes as they belong to the main model:\n\n```php\nclass Student extends Human\n{\n    // ...\n\n    public function rules()\n    {\n        return [\n            // ...\n            ['studyGroupId', 'integer'],\n            ['hasScholarship', 'boolean'],\n        ];\n    }\n}\n```\n\n\n## Saving role data \u003cspan id=\"saving-role-data\"\u003e\u003c/span\u003e\n\nWhen main model is saved the related role model will be saved as well:\n\n```php\n$model = new Student();\n$model-\u003ename = 'John Doe';\n$model-\u003eaddress = 'Wall Street, 12';\n$model-\u003estudyGroupId = 14;\n$model-\u003esave(); // insert one record to the 'Human' table and one record - to the 'Student' table\n```\n\nWhen main model is deleted related role model will be delete as well:\n\n```php\n$student = Student::findOne(17);\n$student-\u003edelete(); // Deletes one record from 'Human' table and one record from 'Student' table\n```\n\n\n## Querying role records \u003cspan id=\"querying-role-records\"\u003e\u003c/span\u003e\n\n[[\\yii2tech\\ar\\role\\RoleBehavior]] works through relations. Thus, in order to make role attributes feature work,\nit will perform an extra query to retrieve the role slave or master model, which may produce performance impact\nin case you are working with several models. In order to reduce number of queries you may use `with()` on the\nrole relation:\n\n```php\n$students = Student::find()-\u003ewith('studentRole')-\u003eall(); // only 2 queries will be performed\nforeach ($students as $student) {\n    echo $student-\u003estudyGroupId . '\u003cbr\u003e';\n}\n\n$instructors = Instructor::find()-\u003ewith('human')-\u003eall(); // only 2 queries will be performed\nforeach ($instructors as $instructor) {\n    echo $instructor-\u003ename . '\u003cbr\u003e';\n}\n```\n\nYou may apply 'with' for the role relation as default scope for the ActiveRecord query:\n\n```php\nclass Instructor extends ActiveRecord\n{\n    // ...\n\n    public static function find()\n    {\n        return parent::find()-\u003ewith('human');\n    }\n}\n```\n\n\u003e Tip: you may name slave table primary key same as master one: use 'id' instead of 'humanId' for it.\n  In this case conditions based on primary key will be always the same. However, this trick may cause extra\n  troubles in case you are using joins for role relations at some point.\n\nIf you need to specify search condition based on fields from both entities and you are using relational database,\nyou can use `joinWith()` method:\n\n```php\n$students = Student::find()\n    -\u003einnerJoinWith('studentRole')\n    -\u003eandWhere(['name' =\u003e 'John']) // condition for 'Human' table\n    -\u003eandWhere(['hasScholarship' =\u003e true]) // condition for 'Student' table\n    -\u003eall();\n```\n\n\u003e Tip: using `joinWith()` will still require an extra SQL query to retrieve relational data.\n  You can use [yii2tech/ar-eagerjoin](https://github.com/yii2tech/ar-eagerjoin) extension to remove this extra query.\n\n\n## Creating role setup web interface \u003cspan id=\"creating-role-setup-web-interface\"\u003e\u003c/span\u003e\n\nFiguratively speaking, [[\\yii2tech\\ar\\role\\RoleBehavior]] merges 2 ActiveRecords into a single one.\nThis means you don't need anything special, while creating web interface for their editing.\nYou may use standard CRUD controller:\n\n```php\nuse yii\\web\\Controller;\n\nclass StudentController extends Controller\n{\n    public function actionCreate()\n    {\n        $model = new Student();\n\n        if ($model-\u003eload(Yii::$app-\u003erequest-\u003epost()) \u0026\u0026 $model-\u003esave()) {\n            return $this-\u003eredirect(['view']);\n        }\n\n        return $this-\u003erender('create', [\n            'model' =\u003e $model,\n        ]);\n    }\n\n    // ...\n}\n```\n\nWhile creating a web form you may use attributes from related role model as they belong to the main one:\n\n```php\n\u003c?php\nuse yii\\helpers\\ArrayHelper;\nuse yii\\helpers\\Html;\nuse yii\\widgets\\ActiveForm;\n\n/* @var $model Student */\n?\u003e\n\u003c?php $form = ActiveForm::begin(); ?\u003e\n\n\u003c?= $form-\u003efield($model, 'name'); ?\u003e\n\u003c?= $form-\u003efield($model, 'address'); ?\u003e\n\n\u003c?= $form-\u003efield($model, 'studyGroupId')-\u003edropDownList(ArrayHelper::map(StudyGroup::find()-\u003eall(), 'id', 'name')); ?\u003e\n\u003c?= $form-\u003efield($model, 'hasScholarship')-\u003echeckbox(); ?\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\nFor the best integration you may as well merge labels and hints of the related model:\n\n```php\nclass Student extends Human\n{\n    // ...\n\n    public function attributeLabels()\n    {\n        return array_merge(\n            parent::attributeLabels(),\n            $this-\u003egetRoleRelationModel()-\u003eattributeLabels()\n        );\n    }\n\n    public function attributeHints()\n    {\n        return array_merge(\n            parent::attributeHints(),\n            $this-\u003egetRoleRelationModel()-\u003eattributeHints()\n        );\n    }\n}\n```\n\n**Heads up!** In order to work in this simple way you should declare validation rules for the role model attributes\nbeing 'safe' in the main one:\n\n```php\nclass Student extends Human\n{\n    // ...\n\n    public function rules()\n    {\n        return [\n            // ...\n            [$this-\u003egetRoleRelationModel()-\u003eattributes(), 'safe'],\n        ];\n    }\n}\n```\n\nOtherwise you'll have to load data for the role model separately:\n\n```php\nuse yii\\web\\Controller;\n\nclass StudentController extends Controller\n{\n    public function actionCreate()\n    {\n        $model = new Student();\n\n        $post = Yii::$app-\u003erequest-\u003epost();\n\n        // data loading separated, however only single save required :\n        if ($model-\u003eload($post) \u0026\u0026 $model-\u003egetRoleRelationModel()-\u003eload($post) \u0026\u0026 $model-\u003esave()) {\n            return $this-\u003eredirect(['view']);\n        }\n\n        return $this-\u003erender('create', [\n            'model' =\u003e $model,\n        ]);\n    }\n\n    // ...\n}\n```\n\nYou should use the role model for its inputs while creating form as well:\n\n```php\n\u003c?php\nuse yii\\helpers\\ArrayHelper;\nuse yii\\helpers\\Html;\nuse yii\\widgets\\ActiveForm;\n\n/* @var $model Student */\n?\u003e\n\u003c?php $form = ActiveForm::begin(); ?\u003e\n\n\u003c?= $form-\u003efield($model, 'name'); ?\u003e\n\u003c?= $form-\u003efield($model, 'address'); ?\u003e\n\n\u003c?= $form-\u003efield($model-\u003egetRoleRelationModel(), 'studyGroupId')-\u003edropDownList(ArrayHelper::map(StudyGroup::find()-\u003eall(), 'id', 'name')); ?\u003e\n\u003c?= $form-\u003efield($model-\u003egetRoleRelationModel(), 'hasScholarship')-\u003echeckbox(); ?\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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyii2tech%2Far-role","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyii2tech%2Far-role","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyii2tech%2Far-role/lists"}