├── .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 | [![Packagist Version](https://img.shields.io/packagist/v/omgdef/yii2-multilingual-behavior.svg?style=flat-square)](https://packagist.org/packages/omgdef/yii2-multilingual-behavior) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/omgdef/yii2-multilingual-behavior.svg?style=flat-square)](https://packagist.org/packages/omgdef/yii2-multilingual-behavior) 7 | [![Build Status](https://img.shields.io/travis/OmgDef/yii2-multilingual-behavior/master.svg?style=flat-square)](https://travis-ci.org/OmgDef/yii2-multilingual-behavior) 8 | [![Code Quality](https://img.shields.io/scrutinizer/g/omgdef/yii2-multilingual-behavior/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/OmgDef/yii2-multilingual-behavior) 9 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/omgdef/yii2-multilingual-behavior/master.svg?style=flat-square)](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 |