├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── README.md
├── composer.json
├── composer.lock
├── phpunit.xml.dist
├── src
    ├── MultilingualBehavior.php
    ├── MultilingualQuery.php
    └── MultilingualTrait.php
└── tests
    ├── DatabaseTestCase.php
    ├── DuplicationTest.php
    ├── MultilingualBehaviorAbridgeTest.php
    ├── MultilingualBehaviorTest.php
    ├── bootstrap.php
    ├── data
        ├── test-create-post-na.xml
        ├── test-create-post-set-translations-dublication.xml
        ├── test-create-post-set-translations-na.xml
        ├── test-create-post-set-translations.xml
        ├── test-create-post.xml
        ├── test-delete-post-na.xml
        ├── test-delete-post.xml
        ├── test-dublication.xml
        ├── test-find-posts-na.php
        ├── test-find-posts.php
        ├── test-localized-en.php
        ├── test-localized-ru.php
        ├── test-na.xml
        ├── test-update-not-populated-post-na.xml
        ├── test-update-not-populated-post.xml
        ├── test-update-populated-post-dublication.xml
        ├── test-update-populated-post-na.xml
        ├── test-update-populated-post.xml
        └── test.xml
    ├── migrations
        ├── sqlite.sql
        └── sqlite_with_dublication.sql
    └── models
        ├── Post.php
        ├── PostAbridge.php
        └── PostRequired.php
/.gitignore:
--------------------------------------------------------------------------------
 1 | # phpstorm project files
 2 | .idea
 3 | 
 4 | # netbeans project files
 5 | nbproject
 6 | 
 7 | # zend studio for eclipse project files
 8 | .buildpath
 9 | .project
10 | .settings
11 | 
12 | # windows thumbnail cache
13 | Thumbs.db
14 | 
15 | # composer vendor dir
16 | /vendor
17 | 
18 | # composer itself is not needed
19 | composer.phar
20 | 
21 | # Mac DS_Store Files
22 | .DS_Store
23 | 
24 | # phpunit itself is not needed
25 | phpunit.phar
26 | # local phpunit config
27 | /phpunit.xml
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 |     external_code_coverage: true
3 | checks:
4 |     php:
5 |         code_rating: true
6 |         duplication: true
7 | 
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
 1 | language: php
 2 | 
 3 | php:
 4 |   - 5.4
 5 |   - 5.5
 6 |   - 5.6
 7 |   - 7.0
 8 |   - 7.1
 9 |   - 7.2
10 | 
11 | install:
12 |   - composer self-update
13 |   - composer global require "fxp/composer-asset-plugin:~1.4.1"
14 |   - composer install
15 | 
16 | 
17 | script:
18 |   - phpunit --coverage-clover=coverage.clover
19 | 
20 | after_script:
21 |   - wget https://scrutinizer-ci.com/ocular.phar
22 |   - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
23 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | Yii2 multilingual behavior
  2 | ==========================
  3 | Yii2 port of the [yii-multilingual-behavior](https://github.com/belerophon/yii-multilingual-behavior).
  4 | 
  5 | [](https://packagist.org/packages/omgdef/yii2-multilingual-behavior)
  6 | [](https://packagist.org/packages/omgdef/yii2-multilingual-behavior)
  7 | [](https://travis-ci.org/OmgDef/yii2-multilingual-behavior)
  8 | [](https://scrutinizer-ci.com/g/OmgDef/yii2-multilingual-behavior)
  9 | [](https://scrutinizer-ci.com/g/OmgDef/yii2-multilingual-behavior)
 10 | 
 11 | This behavior allows you to create multilingual models and almost use them as normal models. Translations are stored in a separate table in the database (ex: PostLang or ProductLang) for each model, so you can add or remove a language easily, without modifying your database.
 12 | 
 13 | **!!! IMPORTANT !!! Docs for vesions 1.* [here](https://github.com/OmgDef/yii2-multilingual-behavior/blob/f91d63403f02c8a3266796b41d197068d7ef7fbb/README.md)**
 14 | 
 15 | **In vesion 2.* forceOverwrite property is deprecated**
 16 | 
 17 | Examples
 18 | --------
 19 | 
 20 | Example #1: current language translations are inserted to the model as normal attributes by default.
 21 | 
 22 | ```php
 23 | //Assuming current language is english
 24 | 
 25 | $model = Post::findOne(1);
 26 | echo $model->title; //echo "English title"
 27 | 
 28 | //Now let's imagine current language is french 
 29 | $model = Post::findOne(1);
 30 | echo $model->title; //echo "Titre en Français"
 31 | 
 32 | $model = Post::find()->localized('en')->one();
 33 | echo $model->title; //echo "English title"
 34 | 
 35 | //Current language is still french here
 36 | ```
 37 | 
 38 | Example #2: if you use `multilingual()` in a `find()` query, every model translation is loaded as virtual attributes (title_en, title_fr, title_de, ...).
 39 | 
 40 | ```php
 41 | $model = Post::find()->multilingual()->one();
 42 | echo $model->title_en; //echo "English title"
 43 | echo $model->title_fr; //echo "Titre en Français"
 44 | ```
 45 | 
 46 | Installation
 47 | ------------
 48 | 
 49 | Preferred way to install this extension is through [composer](http://getcomposer.org/download/).
 50 | 
 51 | Either run
 52 | 
 53 | ```
 54 | php composer.phar require --prefer-dist omgdef/yii2-multilingual-behavior
 55 | ```
 56 | 
 57 | or add
 58 | 
 59 | ```
 60 | "omgdef/yii2-multilingual-behavior": "~2.0"
 61 | ```
 62 | 
 63 | to the require section of your `composer.json` file.
 64 | 
 65 | Behavior attributes
 66 | ------------
 67 | Attributes marked as bold are required
 68 | 
 69 | Attribute | Description
 70 | ----------|------------
 71 | languageField | The name of the language field of the translation table. Default is 'language'
 72 | localizedPrefix | The prefix of the localized attributes in the lang table. Is used to avoid collisions in queries. The columns in the translation table corresponding to the localized attributes have to be name like this: ```[prefix]_[name of the attribute]``` and the id column (primary key) like this : ```[prefix]_id```
 73 | requireTranslations | If this property is set to true required validators will be applied to all translation models.
 74 | dynamicLangClass | Dynamically create translation model class. If true, the translation model class will be generated on runtime with the use of the eval() function so no additionnal php file is needed
 75 | langClassName | The name of translation model class. Dafault value is model name + Lang
 76 | **languages** | Available languages. It can be a simple array: ```['fr', 'en']``` or an associative array: ```['fr' => 'Français', 'en' => 'English']```
 77 | **defaultLanguage** | The default language
 78 | **langForeignKey** | Name of the foreign key field of the translation table related to base model table.
 79 | **tableName** | The name of the translation table
 80 | **attributes** | Multilingual attributes
 81 | 
 82 | Usage
 83 | -----
 84 | 
 85 | Here an example of base 'post' table :
 86 | 
 87 | ```sql
 88 | CREATE TABLE IF NOT EXISTS `post` (
 89 |     `id` int(11) NOT NULL AUTO_INCREMENT,
 90 |     `created_at` datetime NOT NULL,
 91 |     `updated_at` datetime NOT NULL,
 92 |     `enabled` tinyint(1) NOT NULL DEFAULT '1',
 93 |     PRIMARY KEY (`id`)
 94 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 95 | ```
 96 | 
 97 | And its associated translation table (configured as default), assuming translated fields are 'title' and 'content':
 98 | 
 99 | ```sql
100 | CREATE TABLE IF NOT EXISTS `postLang` (
101 |     `id` int(11) NOT NULL AUTO_INCREMENT,
102 |     `post_id` int(11) NOT NULL,
103 |     `language` varchar(6) NOT NULL,
104 |     `title` varchar(255) NOT NULL,
105 |     `content` TEXT NOT NULL,
106 |     PRIMARY KEY (`id`),
107 |     KEY `post_id` (`post_id`),
108 |     KEY `language` (`language`)
109 | ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
110 | 
111 | ALTER TABLE `postLang`
112 | ADD CONSTRAINT `postlang_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
113 | ```
114 | 
115 | Attaching this behavior to the model (Post in the example). Commented fields have default values.
116 | 
117 | ```php
118 | public function behaviors()
119 | {
120 |     return [
121 |         'ml' => [
122 |             'class' => MultilingualBehavior::className(),
123 |             'languages' => [
124 |                 'ru' => 'Russian',
125 |                 'en-US' => 'English',
126 |             ],
127 |             //'languageField' => 'language',
128 |             //'localizedPrefix' => '',
129 |             //'requireTranslations' => false',
130 |             //'dynamicLangClass' => true',
131 |             //'langClassName' => PostLang::className(), // or namespace/for/a/class/PostLang
132 |             'defaultLanguage' => 'ru',
133 |             'langForeignKey' => 'post_id',
134 |             'tableName' => "{{%postLang}}",
135 |             'attributes' => [
136 |                 'title', 'content',
137 |             ]
138 |         ],
139 |     ];
140 | }
141 | ```
142 | 
143 | Then you have to overwrite the `find()` method in your model
144 | 
145 | ```php
146 |     public static function find()
147 |     {
148 |         return new MultilingualQuery(get_called_class());
149 |     }
150 | ```
151 | 
152 | As this behavior has ```MultilingualTrait```, you can use it in your query classes
153 | 
154 | ```php
155 | namespace app\models;
156 | 
157 | use yii\db\ActiveQuery;
158 | 
159 | class MultilingualQuery extends ActiveQuery
160 | {
161 |     use MultilingualTrait;
162 | }
163 | ```
164 | 
165 | Form example:
166 | ```php
167 | //title will be saved to model table and as translation for default language
168 | $form->field($model, 'title')->textInput(['maxlength' => 255]);
169 | $form->field($model, 'title_en')->textInput(['maxlength' => 255]);
170 | ```
171 | 
172 | **Hint:** ```$model``` has to be populated with ```translations``` relative data otherwise translations will not be updated after ```$form``` send.
173 | 
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "omgdef/yii2-multilingual-behavior",
 3 |     "description": "Port of the yii-multilingual-behavior for yii",
 4 |     "type": "yii2-extension",
 5 |     "keywords": ["yii2", "extension", " yii2-multilingual-behavior", " behavior", " multilingual", " i18"],
 6 |     "license": "BSD-4-Clause",
 7 |     "authors": [
 8 |         {
 9 |             "name": "OmgDef",
10 |             "email": "omgdef@gmail.com"
11 |         }
12 |     ],
13 |     "autoload": {
14 |         "psr-4": {
15 |             "omgdef\\multilingual\\": "src"
16 |         }
17 |     },
18 |     "require": {
19 |         "yiisoft/yii2": "^2.0.38"
20 |     },
21 |     "require-dev": {
22 |       "phpunit/phpunit": "4.6.*",
23 |       "phpunit/dbunit": ">=1.2"
24 |     },
25 |     "repositories": [
26 |         {
27 |             "type": "composer",
28 |             "url": "https://asset-packagist.org"
29 |         }
30 |     ],
31 |     "config": {
32 |         "allow-plugins": {
33 |             "yiisoft/yii2-composer": true
34 |         },
35 |         "fxp-asset": {
36 |             "enabled": false
37 |         }
38 |     }
39 | }
40 | 
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
   1 | {
   2 |     "_readme": [
   3 |         "This file locks the dependencies of your project to a known state",
   4 |         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
   5 |         "This file is @generated automatically"
   6 |     ],
   7 |     "content-hash": "2c8f2f3578ca520c2f8cd4543b040504",
   8 |     "packages": [
   9 |         {
  10 |             "name": "bower-asset/inputmask",
  11 |             "version": "3.3.11",
  12 |             "source": {
  13 |                 "type": "git",
  14 |                 "url": "git@github.com:RobinHerbots/Inputmask.git",
  15 |                 "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b"
  16 |             },
  17 |             "dist": {
  18 |                 "type": "zip",
  19 |                 "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b",
  20 |                 "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b"
  21 |             },
  22 |             "require": {
  23 |                 "bower-asset/jquery": ">=1.7"
  24 |             },
  25 |             "type": "bower-asset",
  26 |             "license": [
  27 |                 "http://opensource.org/licenses/mit-license.php"
  28 |             ]
  29 |         },
  30 |         {
  31 |             "name": "bower-asset/jquery",
  32 |             "version": "3.6.1",
  33 |             "source": {
  34 |                 "type": "git",
  35 |                 "url": "https://github.com/jquery/jquery-dist.git",
  36 |                 "reference": "3711efedf0ca2e998cd0417324f717f2e0b828ec"
  37 |             },
  38 |             "dist": {
  39 |                 "type": "zip",
  40 |                 "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/3711efedf0ca2e998cd0417324f717f2e0b828ec",
  41 |                 "reference": "3711efedf0ca2e998cd0417324f717f2e0b828ec"
  42 |             },
  43 |             "type": "bower-asset",
  44 |             "license": [
  45 |                 "MIT"
  46 |             ]
  47 |         },
  48 |         {
  49 |             "name": "bower-asset/punycode",
  50 |             "version": "v1.3.2",
  51 |             "source": {
  52 |                 "type": "git",
  53 |                 "url": "https://github.com/mathiasbynens/punycode.js.git",
  54 |                 "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3"
  55 |             },
  56 |             "dist": {
  57 |                 "type": "zip",
  58 |                 "url": "https://api.github.com/repos/mathiasbynens/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3",
  59 |                 "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3"
  60 |             },
  61 |             "type": "bower-asset"
  62 |         },
  63 |         {
  64 |             "name": "bower-asset/yii2-pjax",
  65 |             "version": "2.0.7.1",
  66 |             "source": {
  67 |                 "type": "git",
  68 |                 "url": "https://github.com/yiisoft/jquery-pjax.git",
  69 |                 "reference": "aef7b953107264f00234902a3880eb50dafc48be"
  70 |             },
  71 |             "dist": {
  72 |                 "type": "zip",
  73 |                 "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/aef7b953107264f00234902a3880eb50dafc48be",
  74 |                 "reference": "aef7b953107264f00234902a3880eb50dafc48be"
  75 |             },
  76 |             "require": {
  77 |                 "bower-asset/jquery": ">=1.8"
  78 |             },
  79 |             "type": "bower-asset",
  80 |             "license": [
  81 |                 "MIT"
  82 |             ]
  83 |         },
  84 |         {
  85 |             "name": "cebe/markdown",
  86 |             "version": "1.2.1",
  87 |             "source": {
  88 |                 "type": "git",
  89 |                 "url": "https://github.com/cebe/markdown.git",
  90 |                 "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86"
  91 |             },
  92 |             "dist": {
  93 |                 "type": "zip",
  94 |                 "url": "https://api.github.com/repos/cebe/markdown/zipball/9bac5e971dd391e2802dca5400bbeacbaea9eb86",
  95 |                 "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86",
  96 |                 "shasum": ""
  97 |             },
  98 |             "require": {
  99 |                 "lib-pcre": "*",
 100 |                 "php": ">=5.4.0"
 101 |             },
 102 |             "require-dev": {
 103 |                 "cebe/indent": "*",
 104 |                 "facebook/xhprof": "*@dev",
 105 |                 "phpunit/phpunit": "4.1.*"
 106 |             },
 107 |             "bin": [
 108 |                 "bin/markdown"
 109 |             ],
 110 |             "type": "library",
 111 |             "extra": {
 112 |                 "branch-alias": {
 113 |                     "dev-master": "1.2.x-dev"
 114 |                 }
 115 |             },
 116 |             "autoload": {
 117 |                 "psr-4": {
 118 |                     "cebe\\markdown\\": ""
 119 |                 }
 120 |             },
 121 |             "notification-url": "https://packagist.org/downloads/",
 122 |             "license": [
 123 |                 "MIT"
 124 |             ],
 125 |             "authors": [
 126 |                 {
 127 |                     "name": "Carsten Brandt",
 128 |                     "email": "mail@cebe.cc",
 129 |                     "homepage": "http://cebe.cc/",
 130 |                     "role": "Creator"
 131 |                 }
 132 |             ],
 133 |             "description": "A super fast, highly extensible markdown parser for PHP",
 134 |             "homepage": "https://github.com/cebe/markdown#readme",
 135 |             "keywords": [
 136 |                 "extensible",
 137 |                 "fast",
 138 |                 "gfm",
 139 |                 "markdown",
 140 |                 "markdown-extra"
 141 |             ],
 142 |             "support": {
 143 |                 "issues": "https://github.com/cebe/markdown/issues",
 144 |                 "source": "https://github.com/cebe/markdown"
 145 |             },
 146 |             "time": "2018-03-26T11:24:36+00:00"
 147 |         },
 148 |         {
 149 |             "name": "ezyang/htmlpurifier",
 150 |             "version": "v4.14.0",
 151 |             "source": {
 152 |                 "type": "git",
 153 |                 "url": "https://github.com/ezyang/htmlpurifier.git",
 154 |                 "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75"
 155 |             },
 156 |             "dist": {
 157 |                 "type": "zip",
 158 |                 "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75",
 159 |                 "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75",
 160 |                 "shasum": ""
 161 |             },
 162 |             "require": {
 163 |                 "php": ">=5.2"
 164 |             },
 165 |             "type": "library",
 166 |             "autoload": {
 167 |                 "files": [
 168 |                     "library/HTMLPurifier.composer.php"
 169 |                 ],
 170 |                 "psr-0": {
 171 |                     "HTMLPurifier": "library/"
 172 |                 },
 173 |                 "exclude-from-classmap": [
 174 |                     "/library/HTMLPurifier/Language/"
 175 |                 ]
 176 |             },
 177 |             "notification-url": "https://packagist.org/downloads/",
 178 |             "license": [
 179 |                 "LGPL-2.1-or-later"
 180 |             ],
 181 |             "authors": [
 182 |                 {
 183 |                     "name": "Edward Z. Yang",
 184 |                     "email": "admin@htmlpurifier.org",
 185 |                     "homepage": "http://ezyang.com"
 186 |                 }
 187 |             ],
 188 |             "description": "Standards compliant HTML filter written in PHP",
 189 |             "homepage": "http://htmlpurifier.org/",
 190 |             "keywords": [
 191 |                 "html"
 192 |             ],
 193 |             "support": {
 194 |                 "issues": "https://github.com/ezyang/htmlpurifier/issues",
 195 |                 "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0"
 196 |             },
 197 |             "time": "2021-12-25T01:21:49+00:00"
 198 |         },
 199 |         {
 200 |             "name": "paragonie/random_compat",
 201 |             "version": "v9.99.100",
 202 |             "source": {
 203 |                 "type": "git",
 204 |                 "url": "https://github.com/paragonie/random_compat.git",
 205 |                 "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
 206 |             },
 207 |             "dist": {
 208 |                 "type": "zip",
 209 |                 "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
 210 |                 "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
 211 |                 "shasum": ""
 212 |             },
 213 |             "require": {
 214 |                 "php": ">= 7"
 215 |             },
 216 |             "require-dev": {
 217 |                 "phpunit/phpunit": "4.*|5.*",
 218 |                 "vimeo/psalm": "^1"
 219 |             },
 220 |             "suggest": {
 221 |                 "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
 222 |             },
 223 |             "type": "library",
 224 |             "notification-url": "https://packagist.org/downloads/",
 225 |             "license": [
 226 |                 "MIT"
 227 |             ],
 228 |             "authors": [
 229 |                 {
 230 |                     "name": "Paragon Initiative Enterprises",
 231 |                     "email": "security@paragonie.com",
 232 |                     "homepage": "https://paragonie.com"
 233 |                 }
 234 |             ],
 235 |             "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
 236 |             "keywords": [
 237 |                 "csprng",
 238 |                 "polyfill",
 239 |                 "pseudorandom",
 240 |                 "random"
 241 |             ],
 242 |             "support": {
 243 |                 "email": "info@paragonie.com",
 244 |                 "issues": "https://github.com/paragonie/random_compat/issues",
 245 |                 "source": "https://github.com/paragonie/random_compat"
 246 |             },
 247 |             "time": "2020-10-15T08:29:30+00:00"
 248 |         },
 249 |         {
 250 |             "name": "yiisoft/yii2",
 251 |             "version": "2.0.46",
 252 |             "source": {
 253 |                 "type": "git",
 254 |                 "url": "https://github.com/yiisoft/yii2-framework.git",
 255 |                 "reference": "d73259c3bc886648a6875109f9f09cddeff03708"
 256 |             },
 257 |             "dist": {
 258 |                 "type": "zip",
 259 |                 "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/d73259c3bc886648a6875109f9f09cddeff03708",
 260 |                 "reference": "d73259c3bc886648a6875109f9f09cddeff03708",
 261 |                 "shasum": ""
 262 |             },
 263 |             "require": {
 264 |                 "bower-asset/inputmask": "~3.2.2 | ~3.3.5",
 265 |                 "bower-asset/jquery": "3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
 266 |                 "bower-asset/punycode": "1.3.*",
 267 |                 "bower-asset/yii2-pjax": "~2.0.1",
 268 |                 "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0",
 269 |                 "ext-ctype": "*",
 270 |                 "ext-mbstring": "*",
 271 |                 "ezyang/htmlpurifier": "~4.6",
 272 |                 "lib-pcre": "*",
 273 |                 "paragonie/random_compat": ">=1",
 274 |                 "php": ">=5.4.0",
 275 |                 "yiisoft/yii2-composer": "~2.0.4"
 276 |             },
 277 |             "bin": [
 278 |                 "yii"
 279 |             ],
 280 |             "type": "library",
 281 |             "extra": {
 282 |                 "branch-alias": {
 283 |                     "dev-master": "2.0.x-dev"
 284 |                 }
 285 |             },
 286 |             "autoload": {
 287 |                 "psr-4": {
 288 |                     "yii\\": ""
 289 |                 }
 290 |             },
 291 |             "notification-url": "https://packagist.org/downloads/",
 292 |             "license": [
 293 |                 "BSD-3-Clause"
 294 |             ],
 295 |             "authors": [
 296 |                 {
 297 |                     "name": "Qiang Xue",
 298 |                     "email": "qiang.xue@gmail.com",
 299 |                     "homepage": "https://www.yiiframework.com/",
 300 |                     "role": "Founder and project lead"
 301 |                 },
 302 |                 {
 303 |                     "name": "Alexander Makarov",
 304 |                     "email": "sam@rmcreative.ru",
 305 |                     "homepage": "https://rmcreative.ru/",
 306 |                     "role": "Core framework development"
 307 |                 },
 308 |                 {
 309 |                     "name": "Maurizio Domba",
 310 |                     "homepage": "http://mdomba.info/",
 311 |                     "role": "Core framework development"
 312 |                 },
 313 |                 {
 314 |                     "name": "Carsten Brandt",
 315 |                     "email": "mail@cebe.cc",
 316 |                     "homepage": "https://www.cebe.cc/",
 317 |                     "role": "Core framework development"
 318 |                 },
 319 |                 {
 320 |                     "name": "Timur Ruziev",
 321 |                     "email": "resurtm@gmail.com",
 322 |                     "homepage": "http://resurtm.com/",
 323 |                     "role": "Core framework development"
 324 |                 },
 325 |                 {
 326 |                     "name": "Paul Klimov",
 327 |                     "email": "klimov.paul@gmail.com",
 328 |                     "role": "Core framework development"
 329 |                 },
 330 |                 {
 331 |                     "name": "Dmitry Naumenko",
 332 |                     "email": "d.naumenko.a@gmail.com",
 333 |                     "role": "Core framework development"
 334 |                 },
 335 |                 {
 336 |                     "name": "Boudewijn Vahrmeijer",
 337 |                     "email": "info@dynasource.eu",
 338 |                     "homepage": "http://dynasource.eu",
 339 |                     "role": "Core framework development"
 340 |                 }
 341 |             ],
 342 |             "description": "Yii PHP Framework Version 2",
 343 |             "homepage": "https://www.yiiframework.com/",
 344 |             "keywords": [
 345 |                 "framework",
 346 |                 "yii2"
 347 |             ],
 348 |             "support": {
 349 |                 "forum": "https://forum.yiiframework.com/",
 350 |                 "irc": "ircs://irc.libera.chat:6697/yii",
 351 |                 "issues": "https://github.com/yiisoft/yii2/issues?state=open",
 352 |                 "source": "https://github.com/yiisoft/yii2",
 353 |                 "wiki": "https://www.yiiframework.com/wiki"
 354 |             },
 355 |             "funding": [
 356 |                 {
 357 |                     "url": "https://github.com/yiisoft",
 358 |                     "type": "github"
 359 |                 },
 360 |                 {
 361 |                     "url": "https://opencollective.com/yiisoft",
 362 |                     "type": "open_collective"
 363 |                 },
 364 |                 {
 365 |                     "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2",
 366 |                     "type": "tidelift"
 367 |                 }
 368 |             ],
 369 |             "time": "2022-08-18T22:18:45+00:00"
 370 |         },
 371 |         {
 372 |             "name": "yiisoft/yii2-composer",
 373 |             "version": "2.0.10",
 374 |             "source": {
 375 |                 "type": "git",
 376 |                 "url": "https://github.com/yiisoft/yii2-composer.git",
 377 |                 "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510"
 378 |             },
 379 |             "dist": {
 380 |                 "type": "zip",
 381 |                 "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/94bb3f66e779e2774f8776d6e1bdeab402940510",
 382 |                 "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510",
 383 |                 "shasum": ""
 384 |             },
 385 |             "require": {
 386 |                 "composer-plugin-api": "^1.0 | ^2.0"
 387 |             },
 388 |             "require-dev": {
 389 |                 "composer/composer": "^1.0 | ^2.0@dev",
 390 |                 "phpunit/phpunit": "<7"
 391 |             },
 392 |             "type": "composer-plugin",
 393 |             "extra": {
 394 |                 "class": "yii\\composer\\Plugin",
 395 |                 "branch-alias": {
 396 |                     "dev-master": "2.0.x-dev"
 397 |                 }
 398 |             },
 399 |             "autoload": {
 400 |                 "psr-4": {
 401 |                     "yii\\composer\\": ""
 402 |                 }
 403 |             },
 404 |             "notification-url": "https://packagist.org/downloads/",
 405 |             "license": [
 406 |                 "BSD-3-Clause"
 407 |             ],
 408 |             "authors": [
 409 |                 {
 410 |                     "name": "Qiang Xue",
 411 |                     "email": "qiang.xue@gmail.com"
 412 |                 },
 413 |                 {
 414 |                     "name": "Carsten Brandt",
 415 |                     "email": "mail@cebe.cc"
 416 |                 }
 417 |             ],
 418 |             "description": "The composer plugin for Yii extension installer",
 419 |             "keywords": [
 420 |                 "composer",
 421 |                 "extension installer",
 422 |                 "yii2"
 423 |             ],
 424 |             "support": {
 425 |                 "forum": "http://www.yiiframework.com/forum/",
 426 |                 "irc": "irc://irc.freenode.net/yii",
 427 |                 "issues": "https://github.com/yiisoft/yii2-composer/issues",
 428 |                 "source": "https://github.com/yiisoft/yii2-composer",
 429 |                 "wiki": "http://www.yiiframework.com/wiki/"
 430 |             },
 431 |             "funding": [
 432 |                 {
 433 |                     "url": "https://github.com/yiisoft",
 434 |                     "type": "github"
 435 |                 },
 436 |                 {
 437 |                     "url": "https://opencollective.com/yiisoft",
 438 |                     "type": "open_collective"
 439 |                 },
 440 |                 {
 441 |                     "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-composer",
 442 |                     "type": "tidelift"
 443 |                 }
 444 |             ],
 445 |             "time": "2020-06-24T00:04:01+00:00"
 446 |         }
 447 |     ],
 448 |     "packages-dev": [
 449 |         {
 450 |             "name": "doctrine/instantiator",
 451 |             "version": "1.4.1",
 452 |             "source": {
 453 |                 "type": "git",
 454 |                 "url": "https://github.com/doctrine/instantiator.git",
 455 |                 "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
 456 |             },
 457 |             "dist": {
 458 |                 "type": "zip",
 459 |                 "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
 460 |                 "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
 461 |                 "shasum": ""
 462 |             },
 463 |             "require": {
 464 |                 "php": "^7.1 || ^8.0"
 465 |             },
 466 |             "require-dev": {
 467 |                 "doctrine/coding-standard": "^9",
 468 |                 "ext-pdo": "*",
 469 |                 "ext-phar": "*",
 470 |                 "phpbench/phpbench": "^0.16 || ^1",
 471 |                 "phpstan/phpstan": "^1.4",
 472 |                 "phpstan/phpstan-phpunit": "^1",
 473 |                 "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
 474 |                 "vimeo/psalm": "^4.22"
 475 |             },
 476 |             "type": "library",
 477 |             "autoload": {
 478 |                 "psr-4": {
 479 |                     "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
 480 |                 }
 481 |             },
 482 |             "notification-url": "https://packagist.org/downloads/",
 483 |             "license": [
 484 |                 "MIT"
 485 |             ],
 486 |             "authors": [
 487 |                 {
 488 |                     "name": "Marco Pivetta",
 489 |                     "email": "ocramius@gmail.com",
 490 |                     "homepage": "https://ocramius.github.io/"
 491 |                 }
 492 |             ],
 493 |             "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
 494 |             "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
 495 |             "keywords": [
 496 |                 "constructor",
 497 |                 "instantiate"
 498 |             ],
 499 |             "support": {
 500 |                 "issues": "https://github.com/doctrine/instantiator/issues",
 501 |                 "source": "https://github.com/doctrine/instantiator/tree/1.4.1"
 502 |             },
 503 |             "funding": [
 504 |                 {
 505 |                     "url": "https://www.doctrine-project.org/sponsorship.html",
 506 |                     "type": "custom"
 507 |                 },
 508 |                 {
 509 |                     "url": "https://www.patreon.com/phpdoctrine",
 510 |                     "type": "patreon"
 511 |                 },
 512 |                 {
 513 |                     "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
 514 |                     "type": "tidelift"
 515 |                 }
 516 |             ],
 517 |             "time": "2022-03-03T08:28:38+00:00"
 518 |         },
 519 |         {
 520 |             "name": "phpdocumentor/reflection-common",
 521 |             "version": "2.2.0",
 522 |             "source": {
 523 |                 "type": "git",
 524 |                 "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
 525 |                 "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
 526 |             },
 527 |             "dist": {
 528 |                 "type": "zip",
 529 |                 "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
 530 |                 "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
 531 |                 "shasum": ""
 532 |             },
 533 |             "require": {
 534 |                 "php": "^7.2 || ^8.0"
 535 |             },
 536 |             "type": "library",
 537 |             "extra": {
 538 |                 "branch-alias": {
 539 |                     "dev-2.x": "2.x-dev"
 540 |                 }
 541 |             },
 542 |             "autoload": {
 543 |                 "psr-4": {
 544 |                     "phpDocumentor\\Reflection\\": "src/"
 545 |                 }
 546 |             },
 547 |             "notification-url": "https://packagist.org/downloads/",
 548 |             "license": [
 549 |                 "MIT"
 550 |             ],
 551 |             "authors": [
 552 |                 {
 553 |                     "name": "Jaap van Otterdijk",
 554 |                     "email": "opensource@ijaap.nl"
 555 |                 }
 556 |             ],
 557 |             "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
 558 |             "homepage": "http://www.phpdoc.org",
 559 |             "keywords": [
 560 |                 "FQSEN",
 561 |                 "phpDocumentor",
 562 |                 "phpdoc",
 563 |                 "reflection",
 564 |                 "static analysis"
 565 |             ],
 566 |             "support": {
 567 |                 "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
 568 |                 "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
 569 |             },
 570 |             "time": "2020-06-27T09:03:43+00:00"
 571 |         },
 572 |         {
 573 |             "name": "phpdocumentor/reflection-docblock",
 574 |             "version": "5.3.0",
 575 |             "source": {
 576 |                 "type": "git",
 577 |                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
 578 |                 "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
 579 |             },
 580 |             "dist": {
 581 |                 "type": "zip",
 582 |                 "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
 583 |                 "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
 584 |                 "shasum": ""
 585 |             },
 586 |             "require": {
 587 |                 "ext-filter": "*",
 588 |                 "php": "^7.2 || ^8.0",
 589 |                 "phpdocumentor/reflection-common": "^2.2",
 590 |                 "phpdocumentor/type-resolver": "^1.3",
 591 |                 "webmozart/assert": "^1.9.1"
 592 |             },
 593 |             "require-dev": {
 594 |                 "mockery/mockery": "~1.3.2",
 595 |                 "psalm/phar": "^4.8"
 596 |             },
 597 |             "type": "library",
 598 |             "extra": {
 599 |                 "branch-alias": {
 600 |                     "dev-master": "5.x-dev"
 601 |                 }
 602 |             },
 603 |             "autoload": {
 604 |                 "psr-4": {
 605 |                     "phpDocumentor\\Reflection\\": "src"
 606 |                 }
 607 |             },
 608 |             "notification-url": "https://packagist.org/downloads/",
 609 |             "license": [
 610 |                 "MIT"
 611 |             ],
 612 |             "authors": [
 613 |                 {
 614 |                     "name": "Mike van Riel",
 615 |                     "email": "me@mikevanriel.com"
 616 |                 },
 617 |                 {
 618 |                     "name": "Jaap van Otterdijk",
 619 |                     "email": "account@ijaap.nl"
 620 |                 }
 621 |             ],
 622 |             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
 623 |             "support": {
 624 |                 "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
 625 |                 "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
 626 |             },
 627 |             "time": "2021-10-19T17:43:47+00:00"
 628 |         },
 629 |         {
 630 |             "name": "phpdocumentor/type-resolver",
 631 |             "version": "1.6.1",
 632 |             "source": {
 633 |                 "type": "git",
 634 |                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
 635 |                 "reference": "77a32518733312af16a44300404e945338981de3"
 636 |             },
 637 |             "dist": {
 638 |                 "type": "zip",
 639 |                 "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
 640 |                 "reference": "77a32518733312af16a44300404e945338981de3",
 641 |                 "shasum": ""
 642 |             },
 643 |             "require": {
 644 |                 "php": "^7.2 || ^8.0",
 645 |                 "phpdocumentor/reflection-common": "^2.0"
 646 |             },
 647 |             "require-dev": {
 648 |                 "ext-tokenizer": "*",
 649 |                 "psalm/phar": "^4.8"
 650 |             },
 651 |             "type": "library",
 652 |             "extra": {
 653 |                 "branch-alias": {
 654 |                     "dev-1.x": "1.x-dev"
 655 |                 }
 656 |             },
 657 |             "autoload": {
 658 |                 "psr-4": {
 659 |                     "phpDocumentor\\Reflection\\": "src"
 660 |                 }
 661 |             },
 662 |             "notification-url": "https://packagist.org/downloads/",
 663 |             "license": [
 664 |                 "MIT"
 665 |             ],
 666 |             "authors": [
 667 |                 {
 668 |                     "name": "Mike van Riel",
 669 |                     "email": "me@mikevanriel.com"
 670 |                 }
 671 |             ],
 672 |             "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
 673 |             "support": {
 674 |                 "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
 675 |                 "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
 676 |             },
 677 |             "time": "2022-03-15T21:29:03+00:00"
 678 |         },
 679 |         {
 680 |             "name": "phpspec/prophecy",
 681 |             "version": "v1.10.3",
 682 |             "source": {
 683 |                 "type": "git",
 684 |                 "url": "https://github.com/phpspec/prophecy.git",
 685 |                 "reference": "451c3cd1418cf640de218914901e51b064abb093"
 686 |             },
 687 |             "dist": {
 688 |                 "type": "zip",
 689 |                 "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
 690 |                 "reference": "451c3cd1418cf640de218914901e51b064abb093",
 691 |                 "shasum": ""
 692 |             },
 693 |             "require": {
 694 |                 "doctrine/instantiator": "^1.0.2",
 695 |                 "php": "^5.3|^7.0",
 696 |                 "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
 697 |                 "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
 698 |                 "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
 699 |             },
 700 |             "require-dev": {
 701 |                 "phpspec/phpspec": "^2.5 || ^3.2",
 702 |                 "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
 703 |             },
 704 |             "type": "library",
 705 |             "extra": {
 706 |                 "branch-alias": {
 707 |                     "dev-master": "1.10.x-dev"
 708 |                 }
 709 |             },
 710 |             "autoload": {
 711 |                 "psr-4": {
 712 |                     "Prophecy\\": "src/Prophecy"
 713 |                 }
 714 |             },
 715 |             "notification-url": "https://packagist.org/downloads/",
 716 |             "license": [
 717 |                 "MIT"
 718 |             ],
 719 |             "authors": [
 720 |                 {
 721 |                     "name": "Konstantin Kudryashov",
 722 |                     "email": "ever.zet@gmail.com",
 723 |                     "homepage": "http://everzet.com"
 724 |                 },
 725 |                 {
 726 |                     "name": "Marcello Duarte",
 727 |                     "email": "marcello.duarte@gmail.com"
 728 |                 }
 729 |             ],
 730 |             "description": "Highly opinionated mocking framework for PHP 5.3+",
 731 |             "homepage": "https://github.com/phpspec/prophecy",
 732 |             "keywords": [
 733 |                 "Double",
 734 |                 "Dummy",
 735 |                 "fake",
 736 |                 "mock",
 737 |                 "spy",
 738 |                 "stub"
 739 |             ],
 740 |             "support": {
 741 |                 "issues": "https://github.com/phpspec/prophecy/issues",
 742 |                 "source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
 743 |             },
 744 |             "time": "2020-03-05T15:02:03+00:00"
 745 |         },
 746 |         {
 747 |             "name": "phpunit/dbunit",
 748 |             "version": "2.0.3",
 749 |             "source": {
 750 |                 "type": "git",
 751 |                 "url": "https://github.com/sebastianbergmann/dbunit.git",
 752 |                 "reference": "5c35d74549c21ba55d0ea74ba89d191a51f8cf25"
 753 |             },
 754 |             "dist": {
 755 |                 "type": "zip",
 756 |                 "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/5c35d74549c21ba55d0ea74ba89d191a51f8cf25",
 757 |                 "reference": "5c35d74549c21ba55d0ea74ba89d191a51f8cf25",
 758 |                 "shasum": ""
 759 |             },
 760 |             "require": {
 761 |                 "ext-pdo": "*",
 762 |                 "ext-simplexml": "*",
 763 |                 "php": "^5.4 || ^7.0",
 764 |                 "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0",
 765 |                 "symfony/yaml": "^2.1 || ^3.0"
 766 |             },
 767 |             "bin": [
 768 |                 "dbunit"
 769 |             ],
 770 |             "type": "library",
 771 |             "extra": {
 772 |                 "branch-alias": {
 773 |                     "dev-master": "2.0.x-dev"
 774 |                 }
 775 |             },
 776 |             "autoload": {
 777 |                 "classmap": [
 778 |                     "src/"
 779 |                 ]
 780 |             },
 781 |             "notification-url": "https://packagist.org/downloads/",
 782 |             "license": [
 783 |                 "BSD-3-Clause"
 784 |             ],
 785 |             "authors": [
 786 |                 {
 787 |                     "name": "Sebastian Bergmann",
 788 |                     "email": "sb@sebastian-bergmann.de",
 789 |                     "role": "lead"
 790 |                 }
 791 |             ],
 792 |             "description": "DbUnit port for PHP/PHPUnit to support database interaction testing.",
 793 |             "homepage": "https://github.com/sebastianbergmann/dbunit/",
 794 |             "keywords": [
 795 |                 "database",
 796 |                 "testing",
 797 |                 "xunit"
 798 |             ],
 799 |             "support": {
 800 |                 "irc": "irc://irc.freenode.net/phpunit",
 801 |                 "issues": "https://github.com/sebastianbergmann/dbunit/issues",
 802 |                 "source": "https://github.com/sebastianbergmann/dbunit/tree/master"
 803 |             },
 804 |             "abandoned": true,
 805 |             "time": "2016-12-02T14:39:14+00:00"
 806 |         },
 807 |         {
 808 |             "name": "phpunit/php-code-coverage",
 809 |             "version": "2.2.4",
 810 |             "source": {
 811 |                 "type": "git",
 812 |                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
 813 |                 "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
 814 |             },
 815 |             "dist": {
 816 |                 "type": "zip",
 817 |                 "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
 818 |                 "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
 819 |                 "shasum": ""
 820 |             },
 821 |             "require": {
 822 |                 "php": ">=5.3.3",
 823 |                 "phpunit/php-file-iterator": "~1.3",
 824 |                 "phpunit/php-text-template": "~1.2",
 825 |                 "phpunit/php-token-stream": "~1.3",
 826 |                 "sebastian/environment": "^1.3.2",
 827 |                 "sebastian/version": "~1.0"
 828 |             },
 829 |             "require-dev": {
 830 |                 "ext-xdebug": ">=2.1.4",
 831 |                 "phpunit/phpunit": "~4"
 832 |             },
 833 |             "suggest": {
 834 |                 "ext-dom": "*",
 835 |                 "ext-xdebug": ">=2.2.1",
 836 |                 "ext-xmlwriter": "*"
 837 |             },
 838 |             "type": "library",
 839 |             "extra": {
 840 |                 "branch-alias": {
 841 |                     "dev-master": "2.2.x-dev"
 842 |                 }
 843 |             },
 844 |             "autoload": {
 845 |                 "classmap": [
 846 |                     "src/"
 847 |                 ]
 848 |             },
 849 |             "notification-url": "https://packagist.org/downloads/",
 850 |             "license": [
 851 |                 "BSD-3-Clause"
 852 |             ],
 853 |             "authors": [
 854 |                 {
 855 |                     "name": "Sebastian Bergmann",
 856 |                     "email": "sb@sebastian-bergmann.de",
 857 |                     "role": "lead"
 858 |                 }
 859 |             ],
 860 |             "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
 861 |             "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
 862 |             "keywords": [
 863 |                 "coverage",
 864 |                 "testing",
 865 |                 "xunit"
 866 |             ],
 867 |             "support": {
 868 |                 "irc": "irc://irc.freenode.net/phpunit",
 869 |                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
 870 |                 "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/2.2"
 871 |             },
 872 |             "time": "2015-10-06T15:47:00+00:00"
 873 |         },
 874 |         {
 875 |             "name": "phpunit/php-file-iterator",
 876 |             "version": "1.4.5",
 877 |             "source": {
 878 |                 "type": "git",
 879 |                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
 880 |                 "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
 881 |             },
 882 |             "dist": {
 883 |                 "type": "zip",
 884 |                 "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
 885 |                 "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
 886 |                 "shasum": ""
 887 |             },
 888 |             "require": {
 889 |                 "php": ">=5.3.3"
 890 |             },
 891 |             "type": "library",
 892 |             "extra": {
 893 |                 "branch-alias": {
 894 |                     "dev-master": "1.4.x-dev"
 895 |                 }
 896 |             },
 897 |             "autoload": {
 898 |                 "classmap": [
 899 |                     "src/"
 900 |                 ]
 901 |             },
 902 |             "notification-url": "https://packagist.org/downloads/",
 903 |             "license": [
 904 |                 "BSD-3-Clause"
 905 |             ],
 906 |             "authors": [
 907 |                 {
 908 |                     "name": "Sebastian Bergmann",
 909 |                     "email": "sb@sebastian-bergmann.de",
 910 |                     "role": "lead"
 911 |                 }
 912 |             ],
 913 |             "description": "FilterIterator implementation that filters files based on a list of suffixes.",
 914 |             "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
 915 |             "keywords": [
 916 |                 "filesystem",
 917 |                 "iterator"
 918 |             ],
 919 |             "support": {
 920 |                 "irc": "irc://irc.freenode.net/phpunit",
 921 |                 "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
 922 |                 "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5"
 923 |             },
 924 |             "time": "2017-11-27T13:52:08+00:00"
 925 |         },
 926 |         {
 927 |             "name": "phpunit/php-text-template",
 928 |             "version": "1.2.1",
 929 |             "source": {
 930 |                 "type": "git",
 931 |                 "url": "https://github.com/sebastianbergmann/php-text-template.git",
 932 |                 "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
 933 |             },
 934 |             "dist": {
 935 |                 "type": "zip",
 936 |                 "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
 937 |                 "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
 938 |                 "shasum": ""
 939 |             },
 940 |             "require": {
 941 |                 "php": ">=5.3.3"
 942 |             },
 943 |             "type": "library",
 944 |             "autoload": {
 945 |                 "classmap": [
 946 |                     "src/"
 947 |                 ]
 948 |             },
 949 |             "notification-url": "https://packagist.org/downloads/",
 950 |             "license": [
 951 |                 "BSD-3-Clause"
 952 |             ],
 953 |             "authors": [
 954 |                 {
 955 |                     "name": "Sebastian Bergmann",
 956 |                     "email": "sebastian@phpunit.de",
 957 |                     "role": "lead"
 958 |                 }
 959 |             ],
 960 |             "description": "Simple template engine.",
 961 |             "homepage": "https://github.com/sebastianbergmann/php-text-template/",
 962 |             "keywords": [
 963 |                 "template"
 964 |             ],
 965 |             "support": {
 966 |                 "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
 967 |                 "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
 968 |             },
 969 |             "time": "2015-06-21T13:50:34+00:00"
 970 |         },
 971 |         {
 972 |             "name": "phpunit/php-timer",
 973 |             "version": "1.0.9",
 974 |             "source": {
 975 |                 "type": "git",
 976 |                 "url": "https://github.com/sebastianbergmann/php-timer.git",
 977 |                 "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
 978 |             },
 979 |             "dist": {
 980 |                 "type": "zip",
 981 |                 "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
 982 |                 "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
 983 |                 "shasum": ""
 984 |             },
 985 |             "require": {
 986 |                 "php": "^5.3.3 || ^7.0"
 987 |             },
 988 |             "require-dev": {
 989 |                 "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
 990 |             },
 991 |             "type": "library",
 992 |             "extra": {
 993 |                 "branch-alias": {
 994 |                     "dev-master": "1.0-dev"
 995 |                 }
 996 |             },
 997 |             "autoload": {
 998 |                 "classmap": [
 999 |                     "src/"
1000 |                 ]
1001 |             },
1002 |             "notification-url": "https://packagist.org/downloads/",
1003 |             "license": [
1004 |                 "BSD-3-Clause"
1005 |             ],
1006 |             "authors": [
1007 |                 {
1008 |                     "name": "Sebastian Bergmann",
1009 |                     "email": "sb@sebastian-bergmann.de",
1010 |                     "role": "lead"
1011 |                 }
1012 |             ],
1013 |             "description": "Utility class for timing",
1014 |             "homepage": "https://github.com/sebastianbergmann/php-timer/",
1015 |             "keywords": [
1016 |                 "timer"
1017 |             ],
1018 |             "support": {
1019 |                 "issues": "https://github.com/sebastianbergmann/php-timer/issues",
1020 |                 "source": "https://github.com/sebastianbergmann/php-timer/tree/master"
1021 |             },
1022 |             "time": "2017-02-26T11:10:40+00:00"
1023 |         },
1024 |         {
1025 |             "name": "phpunit/php-token-stream",
1026 |             "version": "1.4.12",
1027 |             "source": {
1028 |                 "type": "git",
1029 |                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
1030 |                 "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
1031 |             },
1032 |             "dist": {
1033 |                 "type": "zip",
1034 |                 "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
1035 |                 "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
1036 |                 "shasum": ""
1037 |             },
1038 |             "require": {
1039 |                 "ext-tokenizer": "*",
1040 |                 "php": ">=5.3.3"
1041 |             },
1042 |             "require-dev": {
1043 |                 "phpunit/phpunit": "~4.2"
1044 |             },
1045 |             "type": "library",
1046 |             "extra": {
1047 |                 "branch-alias": {
1048 |                     "dev-master": "1.4-dev"
1049 |                 }
1050 |             },
1051 |             "autoload": {
1052 |                 "classmap": [
1053 |                     "src/"
1054 |                 ]
1055 |             },
1056 |             "notification-url": "https://packagist.org/downloads/",
1057 |             "license": [
1058 |                 "BSD-3-Clause"
1059 |             ],
1060 |             "authors": [
1061 |                 {
1062 |                     "name": "Sebastian Bergmann",
1063 |                     "email": "sebastian@phpunit.de"
1064 |                 }
1065 |             ],
1066 |             "description": "Wrapper around PHP's tokenizer extension.",
1067 |             "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
1068 |             "keywords": [
1069 |                 "tokenizer"
1070 |             ],
1071 |             "support": {
1072 |                 "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
1073 |                 "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4"
1074 |             },
1075 |             "abandoned": true,
1076 |             "time": "2017-12-04T08:55:13+00:00"
1077 |         },
1078 |         {
1079 |             "name": "phpunit/phpunit",
1080 |             "version": "4.6.10",
1081 |             "source": {
1082 |                 "type": "git",
1083 |                 "url": "https://github.com/sebastianbergmann/phpunit.git",
1084 |                 "reference": "7b5fe98b28302a8b25693b2298bca74463336975"
1085 |             },
1086 |             "dist": {
1087 |                 "type": "zip",
1088 |                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7b5fe98b28302a8b25693b2298bca74463336975",
1089 |                 "reference": "7b5fe98b28302a8b25693b2298bca74463336975",
1090 |                 "shasum": ""
1091 |             },
1092 |             "require": {
1093 |                 "ext-dom": "*",
1094 |                 "ext-json": "*",
1095 |                 "ext-pcre": "*",
1096 |                 "ext-reflection": "*",
1097 |                 "ext-spl": "*",
1098 |                 "php": ">=5.3.3",
1099 |                 "phpspec/prophecy": "~1.3,>=1.3.1",
1100 |                 "phpunit/php-code-coverage": "~2.0,>=2.0.11",
1101 |                 "phpunit/php-file-iterator": "~1.4",
1102 |                 "phpunit/php-text-template": "~1.2",
1103 |                 "phpunit/php-timer": "~1.0",
1104 |                 "phpunit/phpunit-mock-objects": "~2.3",
1105 |                 "sebastian/comparator": "~1.1",
1106 |                 "sebastian/diff": "~1.2",
1107 |                 "sebastian/environment": "~1.2",
1108 |                 "sebastian/exporter": "~1.2",
1109 |                 "sebastian/global-state": "~1.0",
1110 |                 "sebastian/version": "~1.0",
1111 |                 "symfony/yaml": "~2.1|~3.0"
1112 |             },
1113 |             "suggest": {
1114 |                 "phpunit/php-invoker": "~1.1"
1115 |             },
1116 |             "bin": [
1117 |                 "phpunit"
1118 |             ],
1119 |             "type": "library",
1120 |             "extra": {
1121 |                 "branch-alias": {
1122 |                     "dev-master": "4.6.x-dev"
1123 |                 }
1124 |             },
1125 |             "autoload": {
1126 |                 "classmap": [
1127 |                     "src/"
1128 |                 ]
1129 |             },
1130 |             "notification-url": "https://packagist.org/downloads/",
1131 |             "license": [
1132 |                 "BSD-3-Clause"
1133 |             ],
1134 |             "authors": [
1135 |                 {
1136 |                     "name": "Sebastian Bergmann",
1137 |                     "email": "sebastian@phpunit.de",
1138 |                     "role": "lead"
1139 |                 }
1140 |             ],
1141 |             "description": "The PHP Unit Testing framework.",
1142 |             "homepage": "https://phpunit.de/",
1143 |             "keywords": [
1144 |                 "phpunit",
1145 |                 "testing",
1146 |                 "xunit"
1147 |             ],
1148 |             "support": {
1149 |                 "irc": "irc://irc.freenode.net/phpunit",
1150 |                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
1151 |                 "source": "https://github.com/sebastianbergmann/phpunit/tree/4.6.10"
1152 |             },
1153 |             "time": "2015-06-03T05:03:30+00:00"
1154 |         },
1155 |         {
1156 |             "name": "phpunit/phpunit-mock-objects",
1157 |             "version": "2.3.8",
1158 |             "source": {
1159 |                 "type": "git",
1160 |                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
1161 |                 "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
1162 |             },
1163 |             "dist": {
1164 |                 "type": "zip",
1165 |                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
1166 |                 "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
1167 |                 "shasum": ""
1168 |             },
1169 |             "require": {
1170 |                 "doctrine/instantiator": "^1.0.2",
1171 |                 "php": ">=5.3.3",
1172 |                 "phpunit/php-text-template": "~1.2",
1173 |                 "sebastian/exporter": "~1.2"
1174 |             },
1175 |             "require-dev": {
1176 |                 "phpunit/phpunit": "~4.4"
1177 |             },
1178 |             "suggest": {
1179 |                 "ext-soap": "*"
1180 |             },
1181 |             "type": "library",
1182 |             "extra": {
1183 |                 "branch-alias": {
1184 |                     "dev-master": "2.3.x-dev"
1185 |                 }
1186 |             },
1187 |             "autoload": {
1188 |                 "classmap": [
1189 |                     "src/"
1190 |                 ]
1191 |             },
1192 |             "notification-url": "https://packagist.org/downloads/",
1193 |             "license": [
1194 |                 "BSD-3-Clause"
1195 |             ],
1196 |             "authors": [
1197 |                 {
1198 |                     "name": "Sebastian Bergmann",
1199 |                     "email": "sb@sebastian-bergmann.de",
1200 |                     "role": "lead"
1201 |                 }
1202 |             ],
1203 |             "description": "Mock Object library for PHPUnit",
1204 |             "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
1205 |             "keywords": [
1206 |                 "mock",
1207 |                 "xunit"
1208 |             ],
1209 |             "support": {
1210 |                 "irc": "irc://irc.freenode.net/phpunit",
1211 |                 "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
1212 |                 "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/2.3"
1213 |             },
1214 |             "abandoned": true,
1215 |             "time": "2015-10-02T06:51:40+00:00"
1216 |         },
1217 |         {
1218 |             "name": "sebastian/comparator",
1219 |             "version": "1.2.4",
1220 |             "source": {
1221 |                 "type": "git",
1222 |                 "url": "https://github.com/sebastianbergmann/comparator.git",
1223 |                 "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
1224 |             },
1225 |             "dist": {
1226 |                 "type": "zip",
1227 |                 "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
1228 |                 "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
1229 |                 "shasum": ""
1230 |             },
1231 |             "require": {
1232 |                 "php": ">=5.3.3",
1233 |                 "sebastian/diff": "~1.2",
1234 |                 "sebastian/exporter": "~1.2 || ~2.0"
1235 |             },
1236 |             "require-dev": {
1237 |                 "phpunit/phpunit": "~4.4"
1238 |             },
1239 |             "type": "library",
1240 |             "extra": {
1241 |                 "branch-alias": {
1242 |                     "dev-master": "1.2.x-dev"
1243 |                 }
1244 |             },
1245 |             "autoload": {
1246 |                 "classmap": [
1247 |                     "src/"
1248 |                 ]
1249 |             },
1250 |             "notification-url": "https://packagist.org/downloads/",
1251 |             "license": [
1252 |                 "BSD-3-Clause"
1253 |             ],
1254 |             "authors": [
1255 |                 {
1256 |                     "name": "Jeff Welch",
1257 |                     "email": "whatthejeff@gmail.com"
1258 |                 },
1259 |                 {
1260 |                     "name": "Volker Dusch",
1261 |                     "email": "github@wallbash.com"
1262 |                 },
1263 |                 {
1264 |                     "name": "Bernhard Schussek",
1265 |                     "email": "bschussek@2bepublished.at"
1266 |                 },
1267 |                 {
1268 |                     "name": "Sebastian Bergmann",
1269 |                     "email": "sebastian@phpunit.de"
1270 |                 }
1271 |             ],
1272 |             "description": "Provides the functionality to compare PHP values for equality",
1273 |             "homepage": "http://www.github.com/sebastianbergmann/comparator",
1274 |             "keywords": [
1275 |                 "comparator",
1276 |                 "compare",
1277 |                 "equality"
1278 |             ],
1279 |             "support": {
1280 |                 "issues": "https://github.com/sebastianbergmann/comparator/issues",
1281 |                 "source": "https://github.com/sebastianbergmann/comparator/tree/1.2"
1282 |             },
1283 |             "time": "2017-01-29T09:50:25+00:00"
1284 |         },
1285 |         {
1286 |             "name": "sebastian/diff",
1287 |             "version": "1.4.3",
1288 |             "source": {
1289 |                 "type": "git",
1290 |                 "url": "https://github.com/sebastianbergmann/diff.git",
1291 |                 "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
1292 |             },
1293 |             "dist": {
1294 |                 "type": "zip",
1295 |                 "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
1296 |                 "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
1297 |                 "shasum": ""
1298 |             },
1299 |             "require": {
1300 |                 "php": "^5.3.3 || ^7.0"
1301 |             },
1302 |             "require-dev": {
1303 |                 "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
1304 |             },
1305 |             "type": "library",
1306 |             "extra": {
1307 |                 "branch-alias": {
1308 |                     "dev-master": "1.4-dev"
1309 |                 }
1310 |             },
1311 |             "autoload": {
1312 |                 "classmap": [
1313 |                     "src/"
1314 |                 ]
1315 |             },
1316 |             "notification-url": "https://packagist.org/downloads/",
1317 |             "license": [
1318 |                 "BSD-3-Clause"
1319 |             ],
1320 |             "authors": [
1321 |                 {
1322 |                     "name": "Kore Nordmann",
1323 |                     "email": "mail@kore-nordmann.de"
1324 |                 },
1325 |                 {
1326 |                     "name": "Sebastian Bergmann",
1327 |                     "email": "sebastian@phpunit.de"
1328 |                 }
1329 |             ],
1330 |             "description": "Diff implementation",
1331 |             "homepage": "https://github.com/sebastianbergmann/diff",
1332 |             "keywords": [
1333 |                 "diff"
1334 |             ],
1335 |             "support": {
1336 |                 "issues": "https://github.com/sebastianbergmann/diff/issues",
1337 |                 "source": "https://github.com/sebastianbergmann/diff/tree/1.4"
1338 |             },
1339 |             "time": "2017-05-22T07:24:03+00:00"
1340 |         },
1341 |         {
1342 |             "name": "sebastian/environment",
1343 |             "version": "1.3.8",
1344 |             "source": {
1345 |                 "type": "git",
1346 |                 "url": "https://github.com/sebastianbergmann/environment.git",
1347 |                 "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
1348 |             },
1349 |             "dist": {
1350 |                 "type": "zip",
1351 |                 "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
1352 |                 "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
1353 |                 "shasum": ""
1354 |             },
1355 |             "require": {
1356 |                 "php": "^5.3.3 || ^7.0"
1357 |             },
1358 |             "require-dev": {
1359 |                 "phpunit/phpunit": "^4.8 || ^5.0"
1360 |             },
1361 |             "type": "library",
1362 |             "extra": {
1363 |                 "branch-alias": {
1364 |                     "dev-master": "1.3.x-dev"
1365 |                 }
1366 |             },
1367 |             "autoload": {
1368 |                 "classmap": [
1369 |                     "src/"
1370 |                 ]
1371 |             },
1372 |             "notification-url": "https://packagist.org/downloads/",
1373 |             "license": [
1374 |                 "BSD-3-Clause"
1375 |             ],
1376 |             "authors": [
1377 |                 {
1378 |                     "name": "Sebastian Bergmann",
1379 |                     "email": "sebastian@phpunit.de"
1380 |                 }
1381 |             ],
1382 |             "description": "Provides functionality to handle HHVM/PHP environments",
1383 |             "homepage": "http://www.github.com/sebastianbergmann/environment",
1384 |             "keywords": [
1385 |                 "Xdebug",
1386 |                 "environment",
1387 |                 "hhvm"
1388 |             ],
1389 |             "support": {
1390 |                 "issues": "https://github.com/sebastianbergmann/environment/issues",
1391 |                 "source": "https://github.com/sebastianbergmann/environment/tree/1.3"
1392 |             },
1393 |             "time": "2016-08-18T05:49:44+00:00"
1394 |         },
1395 |         {
1396 |             "name": "sebastian/exporter",
1397 |             "version": "1.2.2",
1398 |             "source": {
1399 |                 "type": "git",
1400 |                 "url": "https://github.com/sebastianbergmann/exporter.git",
1401 |                 "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
1402 |             },
1403 |             "dist": {
1404 |                 "type": "zip",
1405 |                 "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
1406 |                 "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
1407 |                 "shasum": ""
1408 |             },
1409 |             "require": {
1410 |                 "php": ">=5.3.3",
1411 |                 "sebastian/recursion-context": "~1.0"
1412 |             },
1413 |             "require-dev": {
1414 |                 "ext-mbstring": "*",
1415 |                 "phpunit/phpunit": "~4.4"
1416 |             },
1417 |             "type": "library",
1418 |             "extra": {
1419 |                 "branch-alias": {
1420 |                     "dev-master": "1.3.x-dev"
1421 |                 }
1422 |             },
1423 |             "autoload": {
1424 |                 "classmap": [
1425 |                     "src/"
1426 |                 ]
1427 |             },
1428 |             "notification-url": "https://packagist.org/downloads/",
1429 |             "license": [
1430 |                 "BSD-3-Clause"
1431 |             ],
1432 |             "authors": [
1433 |                 {
1434 |                     "name": "Jeff Welch",
1435 |                     "email": "whatthejeff@gmail.com"
1436 |                 },
1437 |                 {
1438 |                     "name": "Volker Dusch",
1439 |                     "email": "github@wallbash.com"
1440 |                 },
1441 |                 {
1442 |                     "name": "Bernhard Schussek",
1443 |                     "email": "bschussek@2bepublished.at"
1444 |                 },
1445 |                 {
1446 |                     "name": "Sebastian Bergmann",
1447 |                     "email": "sebastian@phpunit.de"
1448 |                 },
1449 |                 {
1450 |                     "name": "Adam Harvey",
1451 |                     "email": "aharvey@php.net"
1452 |                 }
1453 |             ],
1454 |             "description": "Provides the functionality to export PHP variables for visualization",
1455 |             "homepage": "http://www.github.com/sebastianbergmann/exporter",
1456 |             "keywords": [
1457 |                 "export",
1458 |                 "exporter"
1459 |             ],
1460 |             "support": {
1461 |                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
1462 |                 "source": "https://github.com/sebastianbergmann/exporter/tree/master"
1463 |             },
1464 |             "time": "2016-06-17T09:04:28+00:00"
1465 |         },
1466 |         {
1467 |             "name": "sebastian/global-state",
1468 |             "version": "1.1.1",
1469 |             "source": {
1470 |                 "type": "git",
1471 |                 "url": "https://github.com/sebastianbergmann/global-state.git",
1472 |                 "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
1473 |             },
1474 |             "dist": {
1475 |                 "type": "zip",
1476 |                 "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
1477 |                 "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
1478 |                 "shasum": ""
1479 |             },
1480 |             "require": {
1481 |                 "php": ">=5.3.3"
1482 |             },
1483 |             "require-dev": {
1484 |                 "phpunit/phpunit": "~4.2"
1485 |             },
1486 |             "suggest": {
1487 |                 "ext-uopz": "*"
1488 |             },
1489 |             "type": "library",
1490 |             "extra": {
1491 |                 "branch-alias": {
1492 |                     "dev-master": "1.0-dev"
1493 |                 }
1494 |             },
1495 |             "autoload": {
1496 |                 "classmap": [
1497 |                     "src/"
1498 |                 ]
1499 |             },
1500 |             "notification-url": "https://packagist.org/downloads/",
1501 |             "license": [
1502 |                 "BSD-3-Clause"
1503 |             ],
1504 |             "authors": [
1505 |                 {
1506 |                     "name": "Sebastian Bergmann",
1507 |                     "email": "sebastian@phpunit.de"
1508 |                 }
1509 |             ],
1510 |             "description": "Snapshotting of global state",
1511 |             "homepage": "http://www.github.com/sebastianbergmann/global-state",
1512 |             "keywords": [
1513 |                 "global state"
1514 |             ],
1515 |             "support": {
1516 |                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
1517 |                 "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1"
1518 |             },
1519 |             "time": "2015-10-12T03:26:01+00:00"
1520 |         },
1521 |         {
1522 |             "name": "sebastian/recursion-context",
1523 |             "version": "1.0.5",
1524 |             "source": {
1525 |                 "type": "git",
1526 |                 "url": "https://github.com/sebastianbergmann/recursion-context.git",
1527 |                 "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
1528 |             },
1529 |             "dist": {
1530 |                 "type": "zip",
1531 |                 "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
1532 |                 "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
1533 |                 "shasum": ""
1534 |             },
1535 |             "require": {
1536 |                 "php": ">=5.3.3"
1537 |             },
1538 |             "require-dev": {
1539 |                 "phpunit/phpunit": "~4.4"
1540 |             },
1541 |             "type": "library",
1542 |             "extra": {
1543 |                 "branch-alias": {
1544 |                     "dev-master": "1.0.x-dev"
1545 |                 }
1546 |             },
1547 |             "autoload": {
1548 |                 "classmap": [
1549 |                     "src/"
1550 |                 ]
1551 |             },
1552 |             "notification-url": "https://packagist.org/downloads/",
1553 |             "license": [
1554 |                 "BSD-3-Clause"
1555 |             ],
1556 |             "authors": [
1557 |                 {
1558 |                     "name": "Jeff Welch",
1559 |                     "email": "whatthejeff@gmail.com"
1560 |                 },
1561 |                 {
1562 |                     "name": "Sebastian Bergmann",
1563 |                     "email": "sebastian@phpunit.de"
1564 |                 },
1565 |                 {
1566 |                     "name": "Adam Harvey",
1567 |                     "email": "aharvey@php.net"
1568 |                 }
1569 |             ],
1570 |             "description": "Provides functionality to recursively process PHP variables",
1571 |             "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1572 |             "support": {
1573 |                 "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
1574 |                 "source": "https://github.com/sebastianbergmann/recursion-context/tree/master"
1575 |             },
1576 |             "time": "2016-10-03T07:41:43+00:00"
1577 |         },
1578 |         {
1579 |             "name": "sebastian/version",
1580 |             "version": "1.0.6",
1581 |             "source": {
1582 |                 "type": "git",
1583 |                 "url": "https://github.com/sebastianbergmann/version.git",
1584 |                 "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
1585 |             },
1586 |             "dist": {
1587 |                 "type": "zip",
1588 |                 "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1589 |                 "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1590 |                 "shasum": ""
1591 |             },
1592 |             "type": "library",
1593 |             "autoload": {
1594 |                 "classmap": [
1595 |                     "src/"
1596 |                 ]
1597 |             },
1598 |             "notification-url": "https://packagist.org/downloads/",
1599 |             "license": [
1600 |                 "BSD-3-Clause"
1601 |             ],
1602 |             "authors": [
1603 |                 {
1604 |                     "name": "Sebastian Bergmann",
1605 |                     "email": "sebastian@phpunit.de",
1606 |                     "role": "lead"
1607 |                 }
1608 |             ],
1609 |             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1610 |             "homepage": "https://github.com/sebastianbergmann/version",
1611 |             "support": {
1612 |                 "issues": "https://github.com/sebastianbergmann/version/issues",
1613 |                 "source": "https://github.com/sebastianbergmann/version/tree/1.0.6"
1614 |             },
1615 |             "time": "2015-06-21T13:59:46+00:00"
1616 |         },
1617 |         {
1618 |             "name": "symfony/polyfill-ctype",
1619 |             "version": "v1.26.0",
1620 |             "source": {
1621 |                 "type": "git",
1622 |                 "url": "https://github.com/symfony/polyfill-ctype.git",
1623 |                 "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
1624 |             },
1625 |             "dist": {
1626 |                 "type": "zip",
1627 |                 "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
1628 |                 "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
1629 |                 "shasum": ""
1630 |             },
1631 |             "require": {
1632 |                 "php": ">=7.1"
1633 |             },
1634 |             "provide": {
1635 |                 "ext-ctype": "*"
1636 |             },
1637 |             "suggest": {
1638 |                 "ext-ctype": "For best performance"
1639 |             },
1640 |             "type": "library",
1641 |             "extra": {
1642 |                 "branch-alias": {
1643 |                     "dev-main": "1.26-dev"
1644 |                 },
1645 |                 "thanks": {
1646 |                     "name": "symfony/polyfill",
1647 |                     "url": "https://github.com/symfony/polyfill"
1648 |                 }
1649 |             },
1650 |             "autoload": {
1651 |                 "files": [
1652 |                     "bootstrap.php"
1653 |                 ],
1654 |                 "psr-4": {
1655 |                     "Symfony\\Polyfill\\Ctype\\": ""
1656 |                 }
1657 |             },
1658 |             "notification-url": "https://packagist.org/downloads/",
1659 |             "license": [
1660 |                 "MIT"
1661 |             ],
1662 |             "authors": [
1663 |                 {
1664 |                     "name": "Gert de Pagter",
1665 |                     "email": "BackEndTea@gmail.com"
1666 |                 },
1667 |                 {
1668 |                     "name": "Symfony Community",
1669 |                     "homepage": "https://symfony.com/contributors"
1670 |                 }
1671 |             ],
1672 |             "description": "Symfony polyfill for ctype functions",
1673 |             "homepage": "https://symfony.com",
1674 |             "keywords": [
1675 |                 "compatibility",
1676 |                 "ctype",
1677 |                 "polyfill",
1678 |                 "portable"
1679 |             ],
1680 |             "support": {
1681 |                 "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
1682 |             },
1683 |             "funding": [
1684 |                 {
1685 |                     "url": "https://symfony.com/sponsor",
1686 |                     "type": "custom"
1687 |                 },
1688 |                 {
1689 |                     "url": "https://github.com/fabpot",
1690 |                     "type": "github"
1691 |                 },
1692 |                 {
1693 |                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
1694 |                     "type": "tidelift"
1695 |                 }
1696 |             ],
1697 |             "time": "2022-05-24T11:49:31+00:00"
1698 |         },
1699 |         {
1700 |             "name": "symfony/yaml",
1701 |             "version": "v3.4.47",
1702 |             "source": {
1703 |                 "type": "git",
1704 |                 "url": "https://github.com/symfony/yaml.git",
1705 |                 "reference": "88289caa3c166321883f67fe5130188ebbb47094"
1706 |             },
1707 |             "dist": {
1708 |                 "type": "zip",
1709 |                 "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094",
1710 |                 "reference": "88289caa3c166321883f67fe5130188ebbb47094",
1711 |                 "shasum": ""
1712 |             },
1713 |             "require": {
1714 |                 "php": "^5.5.9|>=7.0.8",
1715 |                 "symfony/polyfill-ctype": "~1.8"
1716 |             },
1717 |             "conflict": {
1718 |                 "symfony/console": "<3.4"
1719 |             },
1720 |             "require-dev": {
1721 |                 "symfony/console": "~3.4|~4.0"
1722 |             },
1723 |             "suggest": {
1724 |                 "symfony/console": "For validating YAML files using the lint command"
1725 |             },
1726 |             "type": "library",
1727 |             "autoload": {
1728 |                 "psr-4": {
1729 |                     "Symfony\\Component\\Yaml\\": ""
1730 |                 },
1731 |                 "exclude-from-classmap": [
1732 |                     "/Tests/"
1733 |                 ]
1734 |             },
1735 |             "notification-url": "https://packagist.org/downloads/",
1736 |             "license": [
1737 |                 "MIT"
1738 |             ],
1739 |             "authors": [
1740 |                 {
1741 |                     "name": "Fabien Potencier",
1742 |                     "email": "fabien@symfony.com"
1743 |                 },
1744 |                 {
1745 |                     "name": "Symfony Community",
1746 |                     "homepage": "https://symfony.com/contributors"
1747 |                 }
1748 |             ],
1749 |             "description": "Symfony Yaml Component",
1750 |             "homepage": "https://symfony.com",
1751 |             "support": {
1752 |                 "source": "https://github.com/symfony/yaml/tree/v3.4.47"
1753 |             },
1754 |             "funding": [
1755 |                 {
1756 |                     "url": "https://symfony.com/sponsor",
1757 |                     "type": "custom"
1758 |                 },
1759 |                 {
1760 |                     "url": "https://github.com/fabpot",
1761 |                     "type": "github"
1762 |                 },
1763 |                 {
1764 |                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
1765 |                     "type": "tidelift"
1766 |                 }
1767 |             ],
1768 |             "time": "2020-10-24T10:57:07+00:00"
1769 |         },
1770 |         {
1771 |             "name": "webmozart/assert",
1772 |             "version": "1.11.0",
1773 |             "source": {
1774 |                 "type": "git",
1775 |                 "url": "https://github.com/webmozarts/assert.git",
1776 |                 "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
1777 |             },
1778 |             "dist": {
1779 |                 "type": "zip",
1780 |                 "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
1781 |                 "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
1782 |                 "shasum": ""
1783 |             },
1784 |             "require": {
1785 |                 "ext-ctype": "*",
1786 |                 "php": "^7.2 || ^8.0"
1787 |             },
1788 |             "conflict": {
1789 |                 "phpstan/phpstan": "<0.12.20",
1790 |                 "vimeo/psalm": "<4.6.1 || 4.6.2"
1791 |             },
1792 |             "require-dev": {
1793 |                 "phpunit/phpunit": "^8.5.13"
1794 |             },
1795 |             "type": "library",
1796 |             "extra": {
1797 |                 "branch-alias": {
1798 |                     "dev-master": "1.10-dev"
1799 |                 }
1800 |             },
1801 |             "autoload": {
1802 |                 "psr-4": {
1803 |                     "Webmozart\\Assert\\": "src/"
1804 |                 }
1805 |             },
1806 |             "notification-url": "https://packagist.org/downloads/",
1807 |             "license": [
1808 |                 "MIT"
1809 |             ],
1810 |             "authors": [
1811 |                 {
1812 |                     "name": "Bernhard Schussek",
1813 |                     "email": "bschussek@gmail.com"
1814 |                 }
1815 |             ],
1816 |             "description": "Assertions to validate method input/output with nice error messages.",
1817 |             "keywords": [
1818 |                 "assert",
1819 |                 "check",
1820 |                 "validate"
1821 |             ],
1822 |             "support": {
1823 |                 "issues": "https://github.com/webmozarts/assert/issues",
1824 |                 "source": "https://github.com/webmozarts/assert/tree/1.11.0"
1825 |             },
1826 |             "time": "2022-06-03T18:03:27+00:00"
1827 |         }
1828 |     ],
1829 |     "aliases": [],
1830 |     "minimum-stability": "stable",
1831 |     "stability-flags": [],
1832 |     "prefer-stable": false,
1833 |     "prefer-lowest": false,
1834 |     "platform": [],
1835 |     "platform-dev": [],
1836 |     "plugin-api-version": "2.0.0"
1837 | }
1838 | 
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |         
 5 |             tests
 6 |         
 7 |     
 8 |     
 9 |         
10 |             src
11 |         
12 |     
13 | 
--------------------------------------------------------------------------------
/src/MultilingualBehavior.php:
--------------------------------------------------------------------------------
  1 |  'Français', 'en' => 'English')
 25 |      * For associative arrays, only the keys will be used.
 26 |      * @var array
 27 |      */
 28 |     public $languages;
 29 | 
 30 |     /**
 31 |      * @var string the default language.
 32 |      * Example: 'en'.
 33 |      */
 34 |     public $defaultLanguage;
 35 | 
 36 |     /**
 37 |      * @var string the name of the translation table
 38 |      */
 39 |     public $tableName;
 40 | 
 41 |     /**
 42 |      * @var string the name of translation model class.
 43 |      */
 44 |     public $langClassName;
 45 | 
 46 |     /**
 47 |      * @var string if $langClassName is not set, it will be assumed that $langClassName is
 48 |      * get_class($this->owner) . $this->langClassSuffix
 49 |      */
 50 |     public $langClassSuffix = 'Lang';
 51 | 
 52 |     /**
 53 |      * @var string the name of the foreign key field of the translation table related to base model table.
 54 |      */
 55 |     public $langForeignKey;
 56 | 
 57 |     /**
 58 |      * @var string the prefix of the localized attributes in the lang table. Here to avoid collisions in queries.
 59 |      * In the translation table, the columns corresponding to the localized attributes have to be name like this: 'l_[name of the attribute]'
 60 |      * and the id column (primary key) like this : 'l_id'
 61 |      * Default to ''.
 62 |      */
 63 |     public $localizedPrefix = '';
 64 | 
 65 |     /**
 66 |      * @var string the name of the lang field of the translation table. Default to 'language'.
 67 |      */
 68 |     public $languageField = 'language';
 69 | 
 70 |     /**
 71 |      * @var boolean if this property is set to true required validators will be applied to all translation models.
 72 |      * Default to false.
 73 |      */
 74 |     public $requireTranslations = false;
 75 | 
 76 |     /**
 77 |      * @var boolean whether to force deletion of the associated translations when a base model is deleted.
 78 |      * Not needed if using foreign key with 'on delete cascade'.
 79 |      * Default to true.
 80 |      */
 81 |     public $forceDelete = true;
 82 | 
 83 |     /**
 84 |      * @var boolean whether to dynamically create translation model class.
 85 |      * If true, the translation model class will be generated on runtime with the use of the eval() function so no additional php file is needed.
 86 |      * See {@link createLangClass()}
 87 |      * Default to true.
 88 |      */
 89 |     public $dynamicLangClass = true;
 90 | 
 91 |     /**
 92 |      * @var boolean whether to abridge the language ID.
 93 |      * Default to true.
 94 |      */
 95 |     public $abridge = true;
 96 | 
 97 |     public $currentLanguage;
 98 | 
 99 |     private $ownerClassName;
100 |     private $ownerPrimaryKey;
101 |     private $langClassShortName;
102 |     private $ownerClassShortName;
103 |     private $langAttributes = [];
104 | 
105 |     /**
106 |      * @var array excluded validators
107 |      */
108 |     private $excludedValidators = ['unique'];
109 | 
110 |     /**
111 |      * @inheritdoc
112 |      */
113 |     public function events()
114 |     {
115 |         return [
116 |             ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
117 |             ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
118 |             ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
119 |             ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
120 |             ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
121 |         ];
122 |     }
123 | 
124 |     /**
125 |      * @inheritdoc
126 |      */
127 |     public function attach($owner)
128 |     {
129 |         /** @var ActiveRecord $owner */
130 |         parent::attach($owner);
131 | 
132 |         if (empty($this->languages) || !is_array($this->languages)) {
133 |             throw new InvalidConfigException('Please specify array of available languages for the ' . get_class($this) . ' in the '
134 |                 . get_class($this->owner) . ' or in the application parameters', 101);
135 |         }
136 | 
137 |         if (array_values($this->languages) !== $this->languages) { //associative array
138 |             $this->languages = array_keys($this->languages);
139 |         }
140 | 
141 |         $this->languages = array_unique(array_map(function ($language) {
142 |             return $this->getLanguageBaseName($language);
143 |         }, $this->languages));
144 | 
145 |         if (!$this->defaultLanguage) {
146 |             $this->defaultLanguage = isset(Yii::$app->params['defaultLanguage']) && Yii::$app->params['defaultLanguage'] ?
147 |                 Yii::$app->params['defaultLanguage'] : Yii::$app->language;
148 |         }
149 | 
150 |         $this->defaultLanguage = $this->getLanguageBaseName($this->defaultLanguage);
151 | 
152 |         if (!$this->currentLanguage) {
153 |             $this->currentLanguage = $this->getLanguageBaseName(Yii::$app->language);
154 |         }
155 | 
156 |         if (empty($this->attributes) || !is_array($this->attributes)) {
157 |             throw new InvalidConfigException('Please specify multilingual attributes for the ' . get_class($this) . ' in the '
158 |                 . get_class($this->owner), 103);
159 |         }
160 | 
161 |         if (!$this->langClassName) {
162 |             $this->langClassName = get_class($this->owner) . $this->langClassSuffix;
163 |         }
164 | 
165 |         $this->langClassShortName = $this->getShortClassName($this->langClassName);
166 |         $this->ownerClassName = get_class($this->owner);
167 |         $this->ownerClassShortName = $this->getShortClassName($this->ownerClassName);
168 | 
169 |         /** @var ActiveRecord $className */
170 |         $className = $this->ownerClassName;
171 |         $this->ownerPrimaryKey = $className::primaryKey()[0];
172 | 
173 |         if (!isset($this->langForeignKey)) {
174 |             throw new InvalidConfigException('Please specify langForeignKey for the ' . get_class($this) . ' in the '
175 |                 . get_class($this->owner), 105);
176 |         }
177 | 
178 |         $rules = $owner->rules();
179 |         $validators = $owner->getValidators();
180 | 
181 |         foreach ($rules as $rule) {
182 |             if (in_array($rule[1], $this->excludedValidators))
183 |                 continue;
184 | 
185 |             $rule_attributes = is_array($rule[0]) ? $rule[0] : [$rule[0]];
186 |             $attributes = array_intersect($this->attributes, $rule_attributes);
187 | 
188 |             if (empty($attributes))
189 |                 continue;
190 | 
191 |             $rule_attributes = [];
192 |             foreach ($attributes as $key => $attribute) {
193 |                 foreach ($this->languages as $language)
194 |                     if ($language != $this->defaultLanguage)
195 |                         $rule_attributes[] = $this->getAttributeName($attribute, $language);
196 |             }
197 | 
198 |             if (isset($rule['skipOnEmpty']) && !$rule['skipOnEmpty'])
199 |                 $rule['skipOnEmpty'] = !$this->requireTranslations;
200 | 
201 |             $params = array_slice($rule, 2);
202 | 
203 |             if ($rule[1] !== 'required' || $this->requireTranslations) {
204 |                 $validators[] = Validator::createValidator($rule[1], $owner, $rule_attributes, $params);
205 |             } elseif ($rule[1] === 'required') {
206 |                 $validators[] = Validator::createValidator('safe', $owner, $rule_attributes, $params);
207 |             }
208 |         }
209 | 
210 |         if ($this->dynamicLangClass) {
211 |             $this->createLangClass();
212 |         }
213 | 
214 |         $translation = new $this->langClassName;
215 |         foreach ($this->languages as $lang) {
216 |             foreach ($this->attributes as $attribute) {
217 |                 $attributeName = $this->localizedPrefix . $attribute;
218 |                 $this->setLangAttribute($this->getAttributeName($attribute, $lang), $translation->{$attributeName});
219 |                 if ($lang == $this->defaultLanguage) {
220 |                     $this->setLangAttribute($attribute, $translation->{$attributeName});
221 |                 }
222 |             }
223 |         }
224 |     }
225 | 
226 |     public function createLangClass()
227 |     {
228 |         if (!class_exists($this->langClassName, false)) {
229 |             $namespace = substr($this->langClassName, 0, strrpos($this->langClassName, '\\'));
230 |             eval('
231 |             namespace ' . $namespace . ';
232 |             use yii\db\ActiveRecord;
233 |             class ' . $this->langClassShortName . ' extends ActiveRecord
234 |             {
235 |                 public static function tableName()
236 |                 {
237 |                     return \'' . $this->tableName . '\';
238 |                 }
239 |             }');
240 |         }
241 |     }
242 | 
243 |     /**
244 |      * Relation to model translations
245 |      * @return ActiveQuery
246 |      */
247 |     public function getTranslations()
248 |     {
249 |         return $this->owner->hasMany($this->langClassName, [$this->langForeignKey => $this->ownerPrimaryKey]);
250 |     }
251 | 
252 |     /**
253 |      * Relation to model translation
254 |      * @param $language
255 |      * @return ActiveQuery
256 |      */
257 |     public function getTranslation($language = null)
258 |     {
259 |         $language = $language ?: $this->getCurrentLanguage();
260 |         return $this->owner->hasOne($this->langClassName, [$this->langForeignKey => $this->ownerPrimaryKey])
261 |             ->where([$this->languageField => $language]);
262 |     }
263 | 
264 |     /**
265 |      * Handle 'beforeValidate' event of the owner.
266 |      */
267 |     public function beforeValidate()
268 |     {
269 |         foreach ($this->attributes as $attribute) {
270 |             $this->setLangAttribute($this->getAttributeName($attribute, $this->defaultLanguage), $this->getLangAttribute($attribute));
271 |         }
272 |     }
273 | 
274 |     /**
275 |      * Handle 'afterFind' event of the owner.
276 |      */
277 |     public function afterFind()
278 |     {
279 |         /** @var ActiveRecord $owner */
280 |         $owner = $this->owner;
281 | 
282 |         if ($owner->isRelationPopulated('translations') && $related = $owner->getRelatedRecords()['translations']) {
283 |             $translations = $this->indexByLanguage($related);
284 |             foreach ($this->languages as $lang) {
285 |                 foreach ($this->attributes as $attribute) {
286 |                     foreach ($translations as $translation) {
287 |                         if ($this->getLanguageBaseName($translation->{$this->languageField}) == $lang) {
288 |                             $attributeName = $this->localizedPrefix . $attribute;
289 |                             $this->setLangAttribute($this->getAttributeName($attribute, $lang), $translation->{$attributeName});
290 | 
291 |                             if ($lang == $this->defaultLanguage) {
292 |                                 $this->setLangAttribute($attribute, $translation->{$attributeName});
293 |                             }
294 |                         }
295 |                     }
296 |                 }
297 |             }
298 |         } else {
299 |             if (!$owner->isRelationPopulated('translation')) {
300 |                 $owner->translation;
301 |             }
302 | 
303 |             $translation = $owner->getRelatedRecords()['translation'];
304 |             if ($translation) {
305 |                 foreach ($this->attributes as $attribute) {
306 |                     $attribute_name = $this->localizedPrefix . $attribute;
307 |                     $owner->setLangAttribute($attribute, $translation->$attribute_name);
308 |                 }
309 |             }
310 |         }
311 | 
312 |         foreach ($this->attributes as $attribute) {
313 |             if ($owner->hasAttribute($attribute) && $this->getLangAttribute($attribute)) {
314 |                 $owner->setAttribute($attribute, $this->getLangAttribute($attribute));
315 |             }
316 |         }
317 |     }
318 | 
319 |     /**
320 |      * Handle 'afterInsert' event of the owner.
321 |      */
322 |     public function afterInsert()
323 |     {
324 |         $this->saveTranslations();
325 |     }
326 | 
327 |     /**
328 |      * Handle 'afterUpdate' event of the owner.
329 |      */
330 |     public function afterUpdate()
331 |     {
332 |         /** @var ActiveRecord $owner */
333 |         $owner = $this->owner;
334 | 
335 |         if ($owner->isRelationPopulated('translations')) {
336 |             $translations = $this->indexByLanguage($owner->getRelatedRecords()['translations']);
337 |             $this->saveTranslations($translations);
338 |         }
339 |     }
340 | 
341 |     /**
342 |      * Handle 'afterDelete' event of the owner.
343 |      */
344 |     public function afterDelete()
345 |     {
346 |         if ($this->forceDelete) {
347 |             /** @var ActiveRecord $owner */
348 |             $owner = $this->owner;
349 |             $owner->unlinkAll('translations', true);
350 |         }
351 |     }
352 | 
353 |     /**
354 |      * @param array $translations
355 |      */
356 |     private function saveTranslations($translations = [])
357 |     {
358 |         /** @var ActiveRecord $owner */
359 |         $owner = $this->owner;
360 | 
361 |         foreach ($this->languages as $lang) {
362 |             $defaultLanguage = $lang == $this->defaultLanguage;
363 | 
364 |             if (!isset($translations[$lang])) {
365 |                 /** @var ActiveRecord $translation */
366 |                 $translation = new $this->langClassName;
367 |                 $translation->{$this->languageField} = $lang;
368 |                 $translation->{$this->langForeignKey} = $owner->getPrimaryKey();
369 |             } else {
370 |                 $translation = $translations[$lang];
371 |             }
372 | 
373 |             $save = false;
374 |             foreach ($this->attributes as $attribute) {
375 |                 $value = $defaultLanguage ? $owner->$attribute : $this->getLangAttribute($this->getAttributeName($attribute, $lang));
376 | 
377 |                 if ($value !== null) {
378 |                     $field = $this->localizedPrefix . $attribute;
379 |                     $translation->$field = $value;
380 |                     $save = true;
381 |                 }
382 |             }
383 | 
384 |             if ($translation->isNewRecord && !$save)
385 |                 continue;
386 | 
387 |             $translation->save();
388 |         }
389 |     }
390 | 
391 |     /**
392 |      * @inheritdoc
393 |      */
394 |     public function canGetProperty($name, $checkVars = true)
395 |     {
396 |         return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)
397 |             || $this->hasLangAttribute($name);
398 |     }
399 | 
400 |     /**
401 |      * @inheritdoc
402 |      */
403 |     public function canSetProperty($name, $checkVars = true)
404 |     {
405 |         return $this->hasLangAttribute($name);
406 |     }
407 | 
408 |     /**
409 |      * @inheritdoc
410 |      */
411 |     public function __get($name)
412 |     {
413 |         try {
414 |             return parent::__get($name);
415 |         } catch (UnknownPropertyException $e) {
416 |             if ($this->hasLangAttribute($name)) return $this->getLangAttribute($name);
417 |             // @codeCoverageIgnoreStart
418 |             else throw $e;
419 |             // @codeCoverageIgnoreEnd
420 |         }
421 |     }
422 | 
423 |     /**
424 |      * @inheritdoc
425 |      */
426 |     public function __set($name, $value)
427 |     {
428 |         try {
429 |             parent::__set($name, $value);
430 |         } catch (UnknownPropertyException $e) {
431 |             if ($this->hasLangAttribute($name)) $this->setLangAttribute($name, $value);
432 |             // @codeCoverageIgnoreStart
433 |             else throw $e;
434 |             // @codeCoverageIgnoreEnd
435 |         }
436 |     }
437 | 
438 |     /**
439 |      * @inheritdoc
440 |      * @codeCoverageIgnore
441 |      */
442 |     public function __isset($name)
443 |     {
444 |         if (!parent::__isset($name)) {
445 |             return $this->hasLangAttribute($name);
446 |         } else {
447 |             return true;
448 |         }
449 |     }
450 | 
451 |     /**
452 |      * Whether an attribute exists
453 |      * @param string $name the name of the attribute
454 |      * @return boolean
455 |      */
456 |     public function hasLangAttribute($name)
457 |     {
458 |         return array_key_exists($name, $this->langAttributes);
459 |     }
460 | 
461 |     /**
462 |      * @param string $name the name of the attribute
463 |      * @return string the attribute value
464 |      */
465 |     public function getLangAttribute($name)
466 |     {
467 |         return $this->hasLangAttribute($name) ? $this->langAttributes[$name] : null;
468 |     }
469 | 
470 |     /**
471 |      * @param string $name the name of the attribute
472 |      * @param string $value the value of the attribute
473 |      */
474 |     public function setLangAttribute($name, $value)
475 |     {
476 |         $this->langAttributes[$name] = $value;
477 |     }
478 | 
479 |     /**
480 |      * @param $records
481 |      *
482 |      * @return array
483 |      * @throws InvalidConfigException
484 |      */
485 |     protected function indexByLanguage(array $records)
486 |     {
487 |         $sorted = [];
488 |         foreach ($records as $record) {
489 |             $sorted[$record->{$this->languageField}] = $record;
490 |         }
491 | 
492 |         unset($records);
493 | 
494 |         return $sorted;
495 |     }
496 | 
497 |     /**
498 |      * @param $language
499 |      * @return string
500 |      */
501 |     protected function getLanguageBaseName($language)
502 |     {
503 |         return $this->abridge ? substr($language, 0, 2) : $language;
504 |     }
505 | 
506 |     /**
507 |      * @param string $className
508 |      * @return string
509 |      */
510 |     private function getShortClassName($className)
511 |     {
512 |         return substr($className, strrpos($className, '\\') + 1);
513 |     }
514 | 
515 |     /**
516 |      * @return mixed|string
517 |      */
518 |     public function getCurrentLanguage()
519 |     {
520 |         return $this->currentLanguage;
521 |     }
522 | 
523 |     /**
524 |      * @param $attribute
525 |      * @param $language
526 |      * @return string
527 |      */
528 |     protected function getAttributeName($attribute, $language)
529 |     {
530 |         $language = $this->abridge ? $language : Inflector::camel2id(Inflector::id2camel($language), "_");
531 |         return $attribute . "_" . $language;
532 |     }
533 | }
534 | 
--------------------------------------------------------------------------------
/src/MultilingualQuery.php:
--------------------------------------------------------------------------------
1 | language;
31 | 
32 |         if (!isset($this->with['translations'])) {
33 |             $this->with(['translation' => function ($query) use ($language, $abridge) {
34 |                 /** @var ActiveQuery $query */
35 |                 $query->where([$this->languageField => $abridge ? substr($language, 0, 2) : $language]);
36 |             }]);
37 |         }
38 | 
39 |         return $this;
40 |     }
41 | 
42 |     /**
43 |      * Scope for querying by all languages
44 |      * @return $this
45 |      */
46 |     public function multilingual()
47 |     {
48 |         if (isset($this->with['translation'])) {
49 |             unset($this->with['translation']);
50 |         }
51 |         $this->with('translations');
52 |         return $this;
53 |     }
54 | }
55 | 
--------------------------------------------------------------------------------
/tests/DatabaseTestCase.php:
--------------------------------------------------------------------------------
 1 | createDefaultDBConnection(\Yii::$app->getDb()->pdo);
25 |     }
26 | 
27 |     /**
28 |      * @inheritdoc
29 |      */
30 |     public function getDataSet()
31 |     {
32 |         return $this->createFlatXMLDataSet(__DIR__ . '/data/test.xml');
33 |     }
34 | 
35 |     /**
36 |      * @inheritdoc
37 |      */
38 |     protected function setUp()
39 |     {
40 |         if (Yii::$app->get('db', false) === null) {
41 |             $this->markTestSkipped();
42 |         } else {
43 |             parent::setUp();
44 |         }
45 |     }
46 | 
47 |     /**
48 |      * @inheritdoc
49 |      */
50 |     public static function setUpBeforeClass()
51 |     {
52 |         try {
53 |             Yii::$app->set('db', [
54 |                 'class' => Connection::className(),
55 |                 'dsn' => 'sqlite::memory:',
56 |             ]);
57 |             Yii::$app->getDb()->open();
58 |             $lines = explode(';', file_get_contents(__DIR__ . '/migrations/' . static::$migrationFileName));
59 |             foreach ($lines as $line) {
60 |                 if (trim($line) !== '') {
61 |                     Yii::$app->getDb()->pdo->exec($line);
62 |                 }
63 |             }
64 |         } catch (\Exception $e) {
65 |             Yii::$app->clear('db');
66 |         }
67 |     }
68 | }
--------------------------------------------------------------------------------
/tests/DuplicationTest.php:
--------------------------------------------------------------------------------
 1 | assertNotNull($post->title);
16 | 
17 |         $post = Post::findOne(1);
18 |         $this->assertNotNull($post->title);
19 | 
20 |         $post = Post::find()->multilingual()->where(['id' => 1])->one();
21 |         $this->assertNotNull($post->title);
22 | 
23 |         $testString = 'TestString';
24 |         $post->title = $testString;
25 |         $this->assertTrue($post->save());
26 | 
27 |         $post = Post::find()->localized('ru')->where(['id' => $post->id])->one();
28 |         $this->assertEquals($testString, $post->title);
29 | 
30 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
31 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-update-populated-post-dublication.xml');
32 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
33 |     }
34 | 
35 |     public function testCreate()
36 |     {
37 |         $post = new Post([
38 |             'title' => 'New post title',
39 |             'body' => 'New post body',
40 |             'title_en' => 'New post title en',
41 |             'body_en' => 'New post body en',
42 |             'title_ru' => 'New post title ru', //this value should be overwritten by default language value
43 |             'body_ru' => 'New post body ru',
44 |         ]);
45 | 
46 |         $this->assertTrue($post->save());
47 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
48 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-create-post-set-translations-dublication.xml');
49 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
50 | 
51 |         $post = new Post([
52 |             'title' => 'New post title',
53 |             'body' => 'New post body',
54 |         ]);
55 | 
56 |         $this->assertTrue($post->save());
57 |         $post = Post::findOne($post->id);
58 |         $this->assertNotNull($post->title);
59 |     }
60 | 
61 |     /**
62 |      * @inheritdoc
63 |      */
64 |     public function getDataSet()
65 |     {
66 |         return $this->createFlatXMLDataSet(__DIR__ . '/data/test-dublication.xml');
67 |     }
68 | }
69 | 
--------------------------------------------------------------------------------
/tests/MultilingualBehaviorAbridgeTest.php:
--------------------------------------------------------------------------------
  1 | detachBehavior('ml');
 16 | 
 17 |         try {
 18 |             $post->attachBehavior('ml', [
 19 |                 'class' => MultilingualBehavior::className(),
 20 |                 'languages' => [],
 21 |             ]);
 22 |             $this->fail("Expected exception not thrown");
 23 |         } catch (InvalidConfigException $e) {
 24 |             $this->assertEquals(101, $e->getCode());
 25 |         }
 26 | 
 27 |         try {
 28 |             $post->detachBehavior('ml');
 29 |             $post->attachBehavior('ml', [
 30 |                 'class' => MultilingualBehavior::className(),
 31 |                 'languages' => 'Some value',
 32 |             ]);
 33 |             $this->fail("Expected exception not thrown");
 34 |         } catch (InvalidConfigException $e) {
 35 |             $this->assertEquals(101, $e->getCode());
 36 |         }
 37 | 
 38 |         try {
 39 |             $post->detachBehavior('ml');
 40 |             $post->attachBehavior('ml', [
 41 |                 'class' => MultilingualBehavior::className(),
 42 |                 'languages' => [
 43 |                     'ru' => 'Russian',
 44 |                     'en-US' => 'English',
 45 |                 ],
 46 |                 'langForeignKey' => 'post_id',
 47 |                 'tableName' => "{{%postLang}}",
 48 |             ]);
 49 |             $this->fail("Expected exception not thrown");
 50 |         } catch (InvalidConfigException $e) {
 51 |             $this->assertEquals(103, $e->getCode());
 52 |         }
 53 | 
 54 |         try {
 55 |             $post->detachBehavior('ml');
 56 |             $post->attachBehavior('ml', [
 57 |                 'class' => MultilingualBehavior::className(),
 58 |                 'languages' => [
 59 |                     'ru' => 'Russian',
 60 |                     'en-US' => 'English',
 61 |                 ],
 62 |                 'attributes' => [
 63 |                     'title', 'body',
 64 |                 ]
 65 |             ]);
 66 |             $this->fail("Expected exception not thrown");
 67 |         } catch (InvalidConfigException $e) {
 68 |             $this->assertEquals(105, $e->getCode());
 69 |         }
 70 | 
 71 |         $post->detachBehavior('ml');
 72 |         $post->attachBehavior('ml', [
 73 |             'class' => MultilingualBehavior::className(),
 74 |             'languages' => [
 75 |                 'ru' => 'Russian',
 76 |                 'en-US' => 'English',
 77 |             ],
 78 |             'langForeignKey' => 'post_id',
 79 |             'attributes' => [
 80 |                 'title', 'body',
 81 |             ]
 82 |         ]);
 83 | 
 84 |         $this->assertNotNull($post->defaultLanguage);
 85 |     }
 86 | 
 87 |     public function testFindPosts()
 88 |     {
 89 |         $data = [];
 90 |         $models = Post::find()->multilingual()->all();
 91 |         foreach ($models as $model) {
 92 |             $this->assertEquals($model->title, $model->title_ru);
 93 |             $this->assertEquals($model->body, $model->body_ru);
 94 |             $this->assertNotNull($model->title_en);
 95 |             $this->assertNotNull($model->body_en);
 96 |             $data[] = $model->toArray([], ['translations']);
 97 |         }
 98 | 
 99 |         $this->assertEquals(require(__DIR__ . '/data/test-find-posts.php'), $data);
100 |     }
101 | 
102 |     public function testCreatePost()
103 |     {
104 |         $post = new Post([
105 |             'title' => 'New post title',
106 |             'body' => 'New post body',
107 |         ]);
108 | 
109 |         $this->assertTrue($post->save());
110 | 
111 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
112 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-create-post.xml');
113 | 
114 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
115 |     }
116 | 
117 |     public function testCreatePostSetTranslations()
118 |     {
119 |         $post = new Post();
120 |         $data = [
121 |             'title' => 'New post title',
122 |             'body' => 'New post body',
123 |             'title_en' => 'New post title en',
124 |             'body_en' => 'New post body en',
125 |             'title_ru' => 'New post title ru', //this value should be overwritten by default language value
126 |             'body_ru' => 'New post body ru',
127 |         ];
128 |         $formName = $post->formName();
129 |         if (!empty($formName)) {
130 |             $data = [$formName => $data];
131 |         }
132 |         $post->load($data);
133 | 
134 |         $this->assertTrue($post->save());
135 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
136 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-create-post-set-translations.xml');
137 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
138 |     }
139 | 
140 |     public function testUpdateNotPopulatedPost()
141 |     {
142 |         $post = Post::findOne(2);
143 |         $post->setAttributes([
144 |             'title' => 'Updated post title 2',
145 |             'body' => 'Updated post body 2',
146 |             'title_en' => 'Updated post title 2 en',
147 |             'body_en' => 'Updated post title 2 en',
148 |         ]);
149 | 
150 |         $this->assertTrue($post->save());
151 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
152 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-update-not-populated-post.xml');
153 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
154 |     }
155 | 
156 |     public function testUpdatePopulatedPost()
157 |     {
158 |         $post = Post::find()->multilingual()->where(['id' => 2])->one();
159 |         $post->setAttributes([
160 |             'title' => 'Updated post title 2',
161 |             'body' => 'Updated post body 2',
162 |             'title_en' => 'Updated post title 2 en',
163 |             'body_en' => 'Updated post body 2 en',
164 |         ]);
165 | 
166 |         $this->assertTrue($post->save());
167 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
168 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-update-populated-post.xml');
169 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
170 |     }
171 | 
172 |     public function testLocalized()
173 |     {
174 |         $post = Post::find()->localized()->where(['id' => 2])->one();
175 |         $this->assertEquals(require(__DIR__ . '/data/test-localized-en.php'), [
176 |             'id' => $post->id,
177 |             'title' => $post->title,
178 |             'body' => $post->body,
179 |         ]);
180 | 
181 |         $post = Post::find()->localized('ru')->where(['id' => 2])->one();
182 |         $this->assertEquals(require(__DIR__ . '/data/test-localized-ru.php'), [
183 |             'id' => $post->id,
184 |             'title' => $post->title,
185 |             'body' => $post->body,
186 |         ]);
187 |     }
188 | 
189 |     public function testDeletePost()
190 |     {
191 |         $post = Post::findOne(2);
192 |         $this->assertEquals(1, $post->delete());
193 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
194 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-delete-post.xml');
195 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
196 |     }
197 | 
198 |     public function testLocalizedAndMultilingual()
199 |     {
200 |         $post = Post::find()->localized()->multilingual()->limit(1)->one();
201 |         $this->assertTrue($post->isRelationPopulated('translations'));
202 |         $this->assertFalse($post->isRelationPopulated('translation'));
203 |     }
204 | 
205 |     public function testRequired()
206 |     {
207 |         $post = new PostRequired([
208 |             'title' => 'rus',
209 |             'body' => 'rus',
210 |         ]);
211 | 
212 |         $post->validate();
213 |         $this->assertArrayNotHasKey('title_ru', $post->errors);
214 |         $this->assertArrayNotHasKey('body_ru', $post->errors);
215 |         $this->assertArrayHasKey('title_en', $post->errors);
216 |         $this->assertArrayHasKey('body_en', $post->errors);
217 |     }
218 | }
219 | 
--------------------------------------------------------------------------------
/tests/MultilingualBehaviorTest.php:
--------------------------------------------------------------------------------
  1 | multilingual()->all();
 15 |         foreach ($models as $model) {
 16 |             $this->assertEquals($model->title, $model->title_ru);
 17 |             $this->assertEquals($model->body, $model->body_ru);
 18 |             $this->assertNotNull($model->title_en_us);
 19 |             $this->assertNotNull($model->body_en_us);
 20 |             $data[] = $model->toArray([], ['translations']);
 21 |         }
 22 | 
 23 |         $this->assertEquals(require(__DIR__ . '/data/test-find-posts-na.php'), $data);
 24 |     }
 25 | 
 26 |     public function testCreatePost()
 27 |     {
 28 |         $post = new Post([
 29 |             'title' => 'New post title',
 30 |             'body' => 'New post body',
 31 |         ]);
 32 | 
 33 |         $this->assertTrue($post->save());
 34 | 
 35 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
 36 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-create-post-na.xml');
 37 | 
 38 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
 39 |     }
 40 | 
 41 |     public function testCreatePostSetTranslations()
 42 |     {
 43 |         $post = new Post();
 44 |         $data = [
 45 |             'title' => 'New post title',
 46 |             'body' => 'New post body',
 47 |             'title_en_us' => 'New post title en',
 48 |             'body_en_us' => 'New post body en',
 49 |             'title_ru' => 'New post title ru', //this value should be overwritten by default language value
 50 |             'body_ru' => 'New post body ru',
 51 |         ];
 52 |         $formName = $post->formName();
 53 |         if (!empty($formName)) {
 54 |             $data = [$formName => $data];
 55 |         }
 56 |         $post->load($data);
 57 | 
 58 |         $this->assertTrue($post->save());
 59 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
 60 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-create-post-set-translations-na.xml');
 61 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
 62 |     }
 63 | 
 64 |     public function testUpdateNotPopulatedPost()
 65 |     {
 66 |         $post = Post::findOne(2);
 67 |         $post->setAttributes([
 68 |             'title' => 'Updated post title 2',
 69 |             'body' => 'Updated post body 2',
 70 |             'title_en_us' => 'Updated post title 2 en',
 71 |             'body_en_us' => 'Updated post title 2 en',
 72 |         ]);
 73 | 
 74 |         $this->assertTrue($post->save());
 75 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
 76 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-update-not-populated-post-na.xml');
 77 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
 78 |     }
 79 | 
 80 |     public function testUpdatePopulatedPost()
 81 |     {
 82 |         $post = Post::find()->multilingual()->where(['id' => 2])->one();
 83 |         $post->setAttributes([
 84 |             'title' => 'Updated post title 2',
 85 |             'body' => 'Updated post body 2',
 86 |             'title_en_us' => 'Updated post title 2 en',
 87 |             'body_en_us' => 'Updated post body 2 en',
 88 |         ]);
 89 | 
 90 |         $this->assertTrue($post->save());
 91 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
 92 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-update-populated-post-na.xml');
 93 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
 94 |     }
 95 | 
 96 |     public function testLocalized()
 97 |     {
 98 |         $post = Post::find()->localized(null, false)->where(['id' => 2])->one();
 99 |         $this->assertEquals(require(__DIR__ . '/data/test-localized-en.php'), [
100 |             'id' => $post->id,
101 |             'title' => $post->title,
102 |             'body' => $post->body,
103 |         ]);
104 | 
105 |         $post = Post::find()->localized('ru', false)->where(['id' => 2])->one();
106 |         $this->assertEquals(require(__DIR__ . '/data/test-localized-ru.php'), [
107 |             'id' => $post->id,
108 |             'title' => $post->title,
109 |             'body' => $post->body,
110 |         ]);
111 |     }
112 | 
113 |     public function testDeletePost()
114 |     {
115 |         $post = Post::findOne(2);
116 |         $this->assertEquals(1, $post->delete());
117 |         $dataSet = $this->getConnection()->createDataSet(['post', 'postLang']);
118 |         $expectedDataSet = $this->createFlatXMLDataSet(__DIR__ . '/data/test-delete-post-na.xml');
119 |         $this->assertDataSetsEqual($expectedDataSet, $dataSet);
120 |     }
121 | 
122 |     /**
123 |      * @inheritdoc
124 |      */
125 |     public function getDataSet()
126 |     {
127 |         return $this->createFlatXMLDataSet(__DIR__ . '/data/test-na.xml');
128 |     }
129 | }
130 | 
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
 1 |  'unit',
12 |     'basePath' => __DIR__,
13 | ]);
--------------------------------------------------------------------------------
/tests/data/test-create-post-na.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 |     
14 | 
--------------------------------------------------------------------------------
/tests/data/test-create-post-set-translations-dublication.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 |     
14 |     
15 |     
16 | 
--------------------------------------------------------------------------------
/tests/data/test-create-post-set-translations-na.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 |     
14 |     
15 | 
--------------------------------------------------------------------------------
/tests/data/test-create-post-set-translations.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 |     
14 |     
15 | 
--------------------------------------------------------------------------------
/tests/data/test-create-post.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 |     
14 | 
--------------------------------------------------------------------------------
/tests/data/test-delete-post-na.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     
4 |     
5 |     
6 |     
7 |     
8 |     
9 | 
--------------------------------------------------------------------------------
/tests/data/test-delete-post.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     
4 |     
5 |     
6 |     
7 |     
8 |     
9 | 
--------------------------------------------------------------------------------
/tests/data/test-dublication.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 | 
--------------------------------------------------------------------------------
/tests/data/test-find-posts-na.php:
--------------------------------------------------------------------------------
 1 | 
 5 |         array(
 6 |             'id' => 1,
 7 |             'translations' =>
 8 |                 array(
 9 |                     0 =>
10 |                         array(
11 |                             'id' => 1,
12 |                             'post_id' => 1,
13 |                             'language' => 'ru',
14 |                             'title' => 'Post title 1 ru',
15 |                             'body' => 'Post body 1 ru',
16 |                         ),
17 |                     1 =>
18 |                         array(
19 |                             'id' => 2,
20 |                             'post_id' => 1,
21 |                             'language' => 'en-US',
22 |                             'title' => 'Post title 1 en',
23 |                             'body' => 'Post body 1 en',
24 |                         ),
25 |                 ),
26 |         ),
27 |     1 =>
28 |         array(
29 |             'id' => 2,
30 |             'translations' =>
31 |                 array(
32 |                     0 =>
33 |                         array(
34 |                             'id' => 3,
35 |                             'post_id' => 2,
36 |                             'language' => 'ru',
37 |                             'title' => 'Post title 2 ru',
38 |                             'body' => 'Post body 2 ru',
39 |                         ),
40 |                     1 =>
41 |                         array(
42 |                             'id' => 4,
43 |                             'post_id' => 2,
44 |                             'language' => 'en-US',
45 |                             'title' => 'Post title 2 en',
46 |                             'body' => 'Post body 2 en',
47 |                         ),
48 |                 ),
49 |         ),
50 |     2 =>
51 |         array(
52 |             'id' => 3,
53 |             'translations' =>
54 |                 array(
55 |                     0 =>
56 |                         array(
57 |                             'id' => 5,
58 |                             'post_id' => 3,
59 |                             'language' => 'ru',
60 |                             'title' => 'Post title 3 ru',
61 |                             'body' => 'Post body 3 ru',
62 |                         ),
63 |                     1 =>
64 |                         array(
65 |                             'id' => 6,
66 |                             'post_id' => 3,
67 |                             'language' => 'en-US',
68 |                             'title' => 'Post title 3 en',
69 |                             'body' => 'Post body 3 en',
70 |                         ),
71 |                 ),
72 |         ),
73 | );
74 | 
--------------------------------------------------------------------------------
/tests/data/test-find-posts.php:
--------------------------------------------------------------------------------
 1 | 
 5 |         array(
 6 |             'id' => 1,
 7 |             'translations' =>
 8 |                 array(
 9 |                     0 =>
10 |                         array(
11 |                             'id' => 1,
12 |                             'post_id' => 1,
13 |                             'language' => 'ru',
14 |                             'title' => 'Post title 1 ru',
15 |                             'body' => 'Post body 1 ru',
16 |                         ),
17 |                     1 =>
18 |                         array(
19 |                             'id' => 2,
20 |                             'post_id' => 1,
21 |                             'language' => 'en',
22 |                             'title' => 'Post title 1 en',
23 |                             'body' => 'Post body 1 en',
24 |                         ),
25 |                 ),
26 |         ),
27 |     1 =>
28 |         array(
29 |             'id' => 2,
30 |             'translations' =>
31 |                 array(
32 |                     0 =>
33 |                         array(
34 |                             'id' => 3,
35 |                             'post_id' => 2,
36 |                             'language' => 'ru',
37 |                             'title' => 'Post title 2 ru',
38 |                             'body' => 'Post body 2 ru',
39 |                         ),
40 |                     1 =>
41 |                         array(
42 |                             'id' => 4,
43 |                             'post_id' => 2,
44 |                             'language' => 'en',
45 |                             'title' => 'Post title 2 en',
46 |                             'body' => 'Post body 2 en',
47 |                         ),
48 |                 ),
49 |         ),
50 |     2 =>
51 |         array(
52 |             'id' => 3,
53 |             'translations' =>
54 |                 array(
55 |                     0 =>
56 |                         array(
57 |                             'id' => 5,
58 |                             'post_id' => 3,
59 |                             'language' => 'ru',
60 |                             'title' => 'Post title 3 ru',
61 |                             'body' => 'Post body 3 ru',
62 |                         ),
63 |                     1 =>
64 |                         array(
65 |                             'id' => 6,
66 |                             'post_id' => 3,
67 |                             'language' => 'en',
68 |                             'title' => 'Post title 3 en',
69 |                             'body' => 'Post body 3 en',
70 |                         ),
71 |                 ),
72 |         ),
73 | );
74 | 
--------------------------------------------------------------------------------
/tests/data/test-localized-en.php:
--------------------------------------------------------------------------------
1 |  2,
4 |     'title' => 'Post title 2 en',
5 |     'body' => 'Post body 2 en',
6 | );
--------------------------------------------------------------------------------
/tests/data/test-localized-ru.php:
--------------------------------------------------------------------------------
1 |  2,
4 |     'title' => 'Post title 2 ru',
5 |     'body' => 'Post body 2 ru',
6 | );
--------------------------------------------------------------------------------
/tests/data/test-na.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 | 
--------------------------------------------------------------------------------
/tests/data/test-update-not-populated-post-na.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 | 
13 | 
14 | 
--------------------------------------------------------------------------------
/tests/data/test-update-not-populated-post.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 | 
13 | 
14 | 
--------------------------------------------------------------------------------
/tests/data/test-update-populated-post-dublication.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 | 
--------------------------------------------------------------------------------
/tests/data/test-update-populated-post-na.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 | 
--------------------------------------------------------------------------------
/tests/data/test-update-populated-post.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 | 
--------------------------------------------------------------------------------
/tests/data/test.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 | 
--------------------------------------------------------------------------------
/tests/migrations/sqlite.sql:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * SQLite
 3 |  */
 4 | 
 5 | DROP TABLE IF EXISTS "post";
 6 | 
 7 | CREATE TABLE "post" (
 8 |   "id"    INTEGER NOT NULL PRIMARY KEY
 9 | );
10 | 
11 | DROP TABLE IF EXISTS "postLang";
12 | 
13 | CREATE TABLE "postLang" (
14 |   "id"        INTEGER NOT NULL PRIMARY KEY,
15 |   "post_id" INTEGER NOT NULL,
16 |   "language" varchar(6) NOT NULL,
17 |   "title"      TEXT,
18 |   "body"      TEXT
19 | );
20 | 
--------------------------------------------------------------------------------
/tests/migrations/sqlite_with_dublication.sql:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * SQLite
 3 |  */
 4 | 
 5 | DROP TABLE IF EXISTS "post";
 6 | 
 7 | CREATE TABLE "post" (
 8 |   "id"    INTEGER NOT NULL PRIMARY KEY,
 9 |   "title"      TEXT,
10 |   "body"      TEXT
11 | );
12 | 
13 | DROP TABLE IF EXISTS "postLang";
14 | 
15 | CREATE TABLE "postLang" (
16 |   "id"        INTEGER NOT NULL PRIMARY KEY,
17 |   "post_id" INTEGER NOT NULL,
18 |   "language" varchar(6) NOT NULL,
19 |   "title"      TEXT,
20 |   "body"      TEXT
21 | );
22 | 
--------------------------------------------------------------------------------
/tests/models/Post.php:
--------------------------------------------------------------------------------
 1 |  [
33 |                 'class' => MultilingualBehavior::className(),
34 |                 'languages' => [
35 |                     'ru' => 'Russian',
36 |                     'en-US' => 'English',
37 |                 ],
38 |                 'defaultLanguage' => 'ru',
39 |                 'langForeignKey' => 'post_id',
40 |                 'tableName' => "{{%postLang}}",
41 |                 'attributes' => [
42 |                     'title', 'body',
43 |                 ]
44 |             ],
45 |         ];
46 |     }
47 | 
48 |     /**
49 |      * @inheritdoc
50 |      */
51 |     public function rules()
52 |     {
53 |         return [
54 |             [['title', 'body'], 'required'],
55 |             ['title', 'string'],
56 |         ];
57 |     }
58 | 
59 |     /**
60 |      * @inheritdoc
61 |      */
62 |     public function transactions()
63 |     {
64 |         return [
65 |             self::SCENARIO_DEFAULT => self::OP_ALL,
66 |         ];
67 |     }
68 | 
69 |     /**
70 |      * @inheritdoc
71 |      */
72 |     public static function find()
73 |     {
74 |         return new MultilingualQuery(get_called_class());
75 |     }
76 | }
--------------------------------------------------------------------------------
/tests/models/PostRequired.php:
--------------------------------------------------------------------------------
 1 |