diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..ee18d45 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,43 @@ +filter: + excluded_paths: + - tests/* +checks: + php: + code_rating: true + duplication: false + unused_methods: true + unused_parameters: true + argument_type_checks: true + verify_property_names: true + method_calls_on_non_object: false + fix_doc_comments: true + instanceof_class_exists: true + catch_class_exists: true + assignment_of_null_return: false + use_statement_alias_conflict: false + simplify_boolean_return: true + return_doc_comments: true + return_doc_comment_if_not_inferrable: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + parameters_in_camelcaps: true + parameter_doc_comments: true + param_doc_comment_if_not_inferrable: true + properties_in_camelcaps: true + no_long_variable_names: + maximum: '27' + no_goto: true + more_specific_types_in_doc_comments: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: false + fix_line_ending: true + fix_php_opening_tag: true + +coding_style: + php: + spaces: + around_operators: + concatenation: true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..54471ff --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + +# environment variable used in test suite +env: TEST_ENVIRONMENT=travis + +# faster builds on new travis setup not using sudo +sudo: false + +# cache vendor dirs +cache: + directories: + - vendor + - $HOME/.composer/cache + +# install dependencies +install: + - travis_retry composer self-update && composer --version + - travis_retry composer update --prefer-dist --no-interaction + +# run tests +script: + - composer validate --no-check-lock + - composer test diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db114d..e2024ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ Translatable Behavior Change Log -------------------------------- -1.0.0 Under development ------------------------ +1.0.0 April 22, 2018 +-------------------- * Initial release (greeflas) Development started March 18, 2018 diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb4ade8 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +

+ + + +

Translatable behavior

+

+ +[![Build Status](https://travis-ci.org/yiimaker/yii2-translatable.svg?branch=master)](https://travis-ci.org/yiimaker/yii2-translatable) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/yiimaker/yii2-translatable/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/yiimaker/yii2-translatable/?branch=master) +[![Total Downloads](https://poser.pugx.org/yiimaker/yii2-translatable/downloads)](https://packagist.org/packages/yiimaker/yii2-translatable) +[![Latest Stable Version](https://poser.pugx.org/yiimaker/yii2-translatable/v/stable)](https://packagist.org/packages/yiimaker/yii2-translatable) +[![Latest Unstable Version](https://poser.pugx.org/yiimaker/yii2-translatable/v/unstable)](https://packagist.org/packages/yiimaker/yii2-translatable) + +Translatable behavior aggregates logic of linking translations to the primary model. + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +$ composer require yiimaker/yii2-translatable +``` + +or add + +``` +"yiimaker/yii2-translatable": "~1.0" +``` + +to the `require` section of your `composer.json`. + +Usage +----- + +1. Add behavior to the your primary model + +```php +public function behaviors() +{ + return [ + // ... + 'translatable' => [ + 'class' => TranslatableBehavior::className(), + // 'translationRelationName' => 'translations', + // 'translationLanguageAttrName' => 'language', + // 'attributeNamePattern' => '%name% [%language%]', + 'translationAttributeList' => [ + 'title', + 'description', + ], + ], + ]; +} +``` + +2. And use `getTranslation()` or `translateTo()` methods + +```php +// product is an active record model with translatable behavior +$product = new Product(); + +// sets translation for default application language +$product->title = 'PhpStrom 2018.1'; +$product->description = 'Лицензия PhpStrom IDE версия 2018.1'; + +// gets translation for English language +$translation = $product->getTranslation('en'); +$translation->title = 'PhpStrom 2018.1'; +$translation->description = 'License of the PhpStrom IDE version 2018.1'; + +// sets description for French language +$product->translateTo('fr')->description = 'La licence de PhpStorm IDE la version 2018.1'; + +$product->insert(); +``` + +`translateTo()` it's just an alias for `getTranslation()` method. + +After saving the model you can fetch this model from the database and translatable behavior will fetch all translations automatically. + +```php +$product = Product::find() + ->where(['id' => 1]) + ->with('translations') + ->one() +; + +// gets translation for English language +$product->translateTo('en')->description; // License of the PhpStrom IDE version 2018.1 +// gets translation for French language +$product->translateTo('fr')->description; // La licence de PhpStorm IDE la version 2018.1 + +// check whether Ukrainian translation not exists +if (!$product->hasTranslation('uk')) { + $product->translateTo('uk')->description = 'Ліцензія PhpStrom IDE версія 2018.1'; +} + +// update Enlish translation +$product->translateTo('en')->title = 'PhpStorm IDE'; + +$product->update(); +``` + +Tests +----- + +You can run tests with composer command + +``` +$ composer test +``` + +or using following command + +``` +$ codecept build && codecept run +``` + +Contributing +------------ + +For information about contributing please read [CONTRIBUTING.md](CONTRIBUTING.md). + +License +------- + +[![License](https://poser.pugx.org/yiimaker/yii2-translatable/license)](https://packagist.org/packages/yiimaker/yii2-translatable) + +This project is released under the terms of the BSD-3-Clause [license](LICENSE). + +Copyright (c) 2017-2018, Yii Maker diff --git a/codeception.yml b/codeception.yml new file mode 100644 index 0000000..af78aa4 --- /dev/null +++ b/codeception.yml @@ -0,0 +1,26 @@ +actor: Tester +paths: + tests: tests + log: tests/_output + data: tests/_data + support: tests/_support +settings: + bootstrap: _bootstrap.php + colors: true + memory_limit: 1024M +extensions: + enabled: + - Codeception\Extension\RunFailed +modules: + config: + Yii2: + cleanup: false + configFile: 'tests/_config/app.php' + Db: + dsn: 'sqlite:tests/_output/test.db' + user: '' + password: '' + dump: 'tests/_data/dump.sql' + cleanup: true, + populate: true, + reconnect: false diff --git a/composer.json b/composer.json index fcde585..f9e2123 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "yiimaker/yii2-translatable", - "description": "", + "description": "Translatable behavior aggregates logic of linking translations to the primary model", "keywords": ["yii2", "behavior", "translatable", "translation", "translations", "ar"], "type": "yii2-extension", "license": "BSD-3-Clause", diff --git a/composer.lock b/composer.lock index 11281cb..f32566c 100644 --- a/composer.lock +++ b/composer.lock @@ -194,16 +194,16 @@ }, { "name": "yiisoft/yii2", - "version": "2.0.14.2", + "version": "2.0.15.1", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "ef74f3783e964cea477f06e6d6ce209df1f37881" + "reference": "ed3a9e1c4abe206e1c3ce48a6b3624119b79850d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/ef74f3783e964cea477f06e6d6ce209df1f37881", - "reference": "ef74f3783e964cea477f06e6d6ce209df1f37881", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/ed3a9e1c4abe206e1c3ce48a6b3624119b79850d", + "reference": "ed3a9e1c4abe206e1c3ce48a6b3624119b79850d", "shasum": "" }, "require": { @@ -290,20 +290,20 @@ "framework", "yii2" ], - "time": "2018-03-13T14:15:01+00:00" + "time": "2018-03-21T18:36:53+00:00" }, { "name": "yiisoft/yii2-composer", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2" + "reference": "163419f1f197e02f015713b0d4f85598d8f8aa80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", - "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/163419f1f197e02f015713b0d4f85598d8f8aa80", + "reference": "163419f1f197e02f015713b0d4f85598d8f8aa80", "shasum": "" }, "require": { @@ -332,6 +332,10 @@ { "name": "Qiang Xue", "email": "qiang.xue@gmail.com" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" } ], "description": "The composer plugin for Yii extension installer", @@ -340,22 +344,22 @@ "extension installer", "yii2" ], - "time": "2016-12-20T13:26:02+00:00" + "time": "2018-03-21T16:15:55+00:00" } ], "packages-dev": [ { "name": "behat/gherkin", - "version": "v4.5.1", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "74ac03d52c5e23ad8abd5c5cce4ab0e8dc1b530a" + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/74ac03d52c5e23ad8abd5c5cce4ab0e8dc1b530a", - "reference": "74ac03d52c5e23ad8abd5c5cce4ab0e8dc1b530a", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", "shasum": "" }, "require": { @@ -401,62 +405,66 @@ "gherkin", "parser" ], - "time": "2017-08-30T11:04:43+00:00" + "time": "2016-10-30T11:50:56+00:00" }, { "name": "codeception/codeception", - "version": "2.4.0", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "c50789a9a62cc0eefc0252e88a5f04f8c47b55f4" + "reference": "b5391497f9a3c9d0a9c02ae39b53441e413e35a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c50789a9a62cc0eefc0252e88a5f04f8c47b55f4", - "reference": "c50789a9a62cc0eefc0252e88a5f04f8c47b55f4", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b5391497f9a3c9d0a9c02ae39b53441e413e35a8", + "reference": "b5391497f9a3c9d0a9c02ae39b53441e413e35a8", "shasum": "" }, "require": { - "behat/gherkin": "^4.4.0", - "codeception/phpunit-wrapper": "^6.0|^7.0", - "codeception/stub": "^1.0", + "behat/gherkin": "~4.4.0", "ext-json": "*", "ext-mbstring": "*", - "facebook/webdriver": ">=1.1.3 <2.0", + "facebook/webdriver": ">=1.0.1 <2.0", "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", "php": ">=5.4.0 <8.0", - "symfony/browser-kit": ">=2.7 <5.0", - "symfony/console": ">=2.7 <5.0", - "symfony/css-selector": ">=2.7 <5.0", - "symfony/dom-crawler": ">=2.7 <5.0", - "symfony/event-dispatcher": ">=2.7 <5.0", - "symfony/finder": ">=2.7 <5.0", - "symfony/yaml": ">=2.7 <5.0" + "phpunit/php-code-coverage": ">=2.2.4 <6.0", + "phpunit/phpunit": ">4.8.20 <7.0", + "phpunit/phpunit-mock-objects": ">2.3 <5.0", + "sebastian/comparator": ">1.1 <3.0", + "sebastian/diff": "^1.4", + "stecman/symfony-console-completion": "^0.7.0", + "symfony/browser-kit": ">=2.7 <4.0", + "symfony/console": ">=2.7 <4.0", + "symfony/css-selector": ">=2.7 <4.0", + "symfony/dom-crawler": ">=2.7.5 <4.0", + "symfony/event-dispatcher": ">=2.7 <4.0", + "symfony/finder": ">=2.7 <4.0", + "symfony/yaml": ">=2.7 <4.0" }, "require-dev": { "codeception/specify": "~0.3", "facebook/graph-sdk": "~5.3", "flow/jsonpath": "~0.2", + "league/factory-muffin": "^3.0", + "league/factory-muffin-faker": "^1.0", + "mongodb/mongodb": "^1.0", "monolog/monolog": "~1.8", "pda/pheanstalk": "~3.0", "php-amqplib/php-amqplib": "~2.4", "predis/predis": "^1.0", "squizlabs/php_codesniffer": "~2.0", - "symfony/process": ">=2.7 <5.0", + "symfony/process": ">=2.7 <4.0", "vlucas/phpdotenv": "^2.4.0" }, "suggest": { - "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", - "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", "codeception/specify": "BDD-style code blocks", "codeception/verify": "BDD-style assertions", "flow/jsonpath": "For using JSONPath in REST module", "league/factory-muffin": "For DataFactory module", "league/factory-muffin-faker": "For Faker support in DataFactory module", "phpseclib/phpseclib": "for SFTP option in FTP Module", - "stecman/symfony-console-completion": "For BASH autocompletion", "symfony/phpunit-bridge": "For phpunit-bridge support" }, "bin": [ @@ -492,83 +500,7 @@ "functional testing", "unit testing" ], - "time": "2018-02-27T00:09:12+00:00" - }, - { - "name": "codeception/phpunit-wrapper", - "version": "7.0.5", - "source": { - "type": "git", - "url": "https://github.com/Codeception/phpunit-wrapper.git", - "reference": "42c8fdbf301ee36f6b58cb154972f9148fdcb7d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/42c8fdbf301ee36f6b58cb154972f9148fdcb7d4", - "reference": "42c8fdbf301ee36f6b58cb154972f9148fdcb7d4", - "shasum": "" - }, - "require": { - "phpunit/php-code-coverage": "^6.0", - "phpunit/phpunit": "^7.0", - "sebastian/comparator": "^2.0", - "sebastian/diff": "^3.0" - }, - "require-dev": { - "codeception/specify": "*", - "vlucas/phpdotenv": "^2.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Codeception\\PHPUnit\\": "src\\" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Davert", - "email": "davert.php@resend.cc" - } - ], - "description": "PHPUnit classes used by Codeception", - "time": "2018-03-16T10:18:33+00:00" - }, - { - "name": "codeception/stub", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/95fb7a36b81890dd2e5163e7ab31310df6f1bb99", - "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99", - "shasum": "" - }, - "require": { - "phpunit/phpunit-mock-objects": ">2.3 <7.0" - }, - "require-dev": { - "phpunit/phpunit": ">=4.8 <8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Codeception\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "time": "2018-02-18T13:56:56+00:00" + "time": "2017-07-10T19:45:09+00:00" }, { "name": "doctrine/instantiator", @@ -626,40 +558,28 @@ }, { "name": "facebook/webdriver", - "version": "1.5.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "86b5ca2f67173c9d34340845dd690149c886a605" + "reference": "1c98108ba3eb435b681655764de11502a0653705" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/86b5ca2f67173c9d34340845dd690149c886a605", - "reference": "86b5ca2f67173c9d34340845dd690149c886a605", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/1c98108ba3eb435b681655764de11502a0653705", + "reference": "1c98108ba3eb435b681655764de11502a0653705", "shasum": "" }, "require": { - "ext-curl": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0" + "php": ">=5.3.19" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "guzzle/guzzle": "^3.4.1", - "php-coveralls/php-coveralls": "^1.0.2", - "php-mock/php-mock-phpunit": "^1.1", - "phpunit/phpunit": "^5.7", - "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", - "squizlabs/php_codesniffer": "^2.6", - "symfony/var-dumper": "^3.3 || ^4.0" + "phpunit/phpunit": "4.6.*" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-community": "1.5-dev" - } + "suggest": { + "phpdocumentor/phpdocumentor": "2.*" }, + "type": "library", "autoload": { "psr-4": { "Facebook\\WebDriver\\": "lib/" @@ -669,7 +589,7 @@ "license": [ "Apache-2.0" ], - "description": "A PHP client for Selenium WebDriver", + "description": "A PHP client for WebDriver", "homepage": "https://github.com/facebook/php-webdriver", "keywords": [ "facebook", @@ -677,20 +597,20 @@ "selenium", "webdriver" ], - "time": "2017-11-15T11:08:09+00:00" + "time": "2015-12-31T15:58:49+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.3.0", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + "reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", - "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/68d0ea14d5a3f42a20e87632a5f84931e2709c90", + "reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90", "shasum": "" }, "require": { @@ -700,7 +620,7 @@ }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "^4.0 || ^5.0", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4", "psr/log": "^1.0" }, "suggest": { @@ -709,7 +629,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.2-dev" + "dev-master": "6.3-dev" } }, "autoload": { @@ -742,7 +662,7 @@ "rest", "web service" ], - "time": "2017-06-22T18:50:49+00:00" + "time": "2018-03-26T16:33:04+00:00" }, { "name": "guzzlehttp/promises", @@ -1161,23 +1081,23 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.5", + "version": "1.7.6", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { @@ -1220,44 +1140,44 @@ "spy", "stub" ], - "time": "2018-02-19T10:16:54+00:00" + "time": "2018-04-18T13:57:24+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "6.0.1", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba" + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f8ca4b604baf23dab89d87773c28cc07405189ba", - "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.1", + "php": "^7.0", "phpunit/php-file-iterator": "^1.4.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", + "phpunit/php-token-stream": "^2.0.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^3.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-xdebug": "^2.5.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0-dev" + "dev-master": "5.3.x-dev" } }, "autoload": { @@ -1283,7 +1203,7 @@ "testing", "xunit" ], - "time": "2018-02-02T07:01:41+00:00" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1375,28 +1295,28 @@ }, { "name": "phpunit/php-timer", - "version": "2.0.0", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -1411,7 +1331,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", + "email": "sb@sebastian-bergmann.de", "role": "lead" } ], @@ -1420,33 +1340,33 @@ "keywords": [ "timer" ], - "time": "2018-02-01T13:07:23+00:00" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.0", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" + "reference": "791198a2c6254db10131eecfe8c06670700904db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.1" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1469,20 +1389,20 @@ "keywords": [ "tokenizer" ], - "time": "2018-02-01T13:16:43+00:00" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", - "version": "7.0.2", + "version": "6.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9" + "reference": "ff3a76a58ac293657808aefd58c8aaf05945f4d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", - "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ff3a76a58ac293657808aefd58c8aaf05945f4d9", + "reference": "ff3a76a58ac293657808aefd58c8aaf05945f4d9", "shasum": "" }, "require": { @@ -1491,31 +1411,35 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", + "myclabs/deep-copy": "^1.3", "phar-io/manifest": "^1.0.1", "phar-io/version": "^1.0", - "php": "^7.1", + "php": "^7.0", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0", - "phpunit/php-file-iterator": "^1.4.3", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", - "phpunit/phpunit-mock-objects": "^6.0", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1", + "phpunit/php-code-coverage": "^5.2", + "phpunit/php-file-iterator": "^1.4", + "phpunit/php-text-template": "^1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^4.0", + "sebastian/comparator": "^2.0", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^3.0.2", "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", + "sebastian/global-state": "^1.1 || ^2.0", + "sebastian/object-enumerator": "^3.0.2", "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0.1" + "sebastian/version": "^2.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^1.1" }, "bin": [ "phpunit" @@ -1523,7 +1447,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.0-dev" + "dev-master": "6.2.x-dev" } }, "autoload": { @@ -1549,30 +1473,33 @@ "testing", "xunit" ], - "time": "2018-02-26T07:03:12+00:00" + "time": "2017-08-03T13:59:28+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "6.0.1", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53" + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e3249dedc2d99259ccae6affbc2684eac37c2e53", - "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/2f789b59ab89669015ad984afa350c4ec577ade0", + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.5", - "php": "^7.1", + "php": "^7.0", "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" + "sebastian/exporter": "^3.0" + }, + "conflict": { + "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-soap": "*" @@ -1580,7 +1507,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -1595,7 +1522,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", + "email": "sb@sebastian-bergmann.de", "role": "lead" } ], @@ -1605,7 +1532,7 @@ "mock", "xunit" ], - "time": "2018-02-15T05:27:38+00:00" + "time": "2017-08-03T14:08:16+00:00" }, { "name": "psr/http-message", @@ -1657,6 +1584,53 @@ ], "time": "2016-08-06T14:39:51+00:00" }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -1704,30 +1678,30 @@ }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "20f84f468cb67efee293246e6a09619b891f55f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", + "reference": "20f84f468cb67efee293246e6a09619b891f55f0", "shasum": "" }, "require": { "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", - "sebastian/exporter": "^3.1" + "sebastian/diff": "^1.2", + "sebastian/exporter": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1758,39 +1732,38 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "homepage": "http://www.github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "time": "2017-03-03T06:26:08+00:00" }, { "name": "sebastian/diff", - "version": "3.0.0", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^7.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -1815,12 +1788,9 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "diff" ], - "time": "2018-02-01T13:45:15+00:00" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -2220,27 +2190,72 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "stecman/symfony-console-completion", + "version": "0.7.0", + "source": { + "type": "git", + "url": "https://github.com/stecman/symfony-console-completion.git", + "reference": "5461d43e53092b3d3b9dbd9d999f2054730f4bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stecman/symfony-console-completion/zipball/5461d43e53092b3d3b9dbd9d999f2054730f4bbb", + "reference": "5461d43e53092b3d3b9dbd9d999f2054730f4bbb", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/console": "~2.3 || ~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stecman\\Component\\Symfony\\Console\\BashCompletion\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Holdaway", + "email": "stephen@stecman.co.nz" + } + ], + "description": "Automatic BASH completion for Symfony Console Component based applications.", + "time": "2016-02-24T05:08:54+00:00" + }, { "name": "symfony/browser-kit", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "fee0fcd501304b1c3190f6293f650cceb738a353" + "reference": "840bb6f0d5b3701fd768b68adf7193c2d0f98f79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/fee0fcd501304b1c3190f6293f650cceb738a353", - "reference": "fee0fcd501304b1c3190f6293f650cceb738a353", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/840bb6f0d5b3701fd768b68adf7193c2d0f98f79", + "reference": "840bb6f0d5b3701fd768b68adf7193c2d0f98f79", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/dom-crawler": "~3.4|~4.0" + "php": "^5.5.9|>=7.0.8", + "symfony/dom-crawler": "~2.8|~3.0|~4.0" }, "require-dev": { - "symfony/css-selector": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0" }, "suggest": { "symfony/process": "" @@ -2248,7 +2263,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2275,24 +2290,25 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:38:00+00:00" + "time": "2018-03-19T22:32:39+00:00" }, { "name": "symfony/console", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "555c8dbe0ae9e561740451eabdbed2cc554b6a51" + "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/555c8dbe0ae9e561740451eabdbed2cc554b6a51", - "reference": "555c8dbe0ae9e561740451eabdbed2cc554b6a51", + "url": "https://api.github.com/repos/symfony/console/zipball/d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", + "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -2301,11 +2317,11 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", + "symfony/config": "~3.3|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.3|~4.0" }, "suggest": { "psr/log": "For using the console logger", @@ -2316,7 +2332,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2343,29 +2359,29 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-02-26T15:55:47+00:00" + "time": "2018-04-03T05:22:50+00:00" }, { "name": "symfony/css-selector", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c69f1e93aa898fd9fec627ebef467188151c8dc2" + "reference": "519a80d7c1d95c6cc0b67f686d15fe27c6910de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c69f1e93aa898fd9fec627ebef467188151c8dc2", - "reference": "c69f1e93aa898fd9fec627ebef467188151c8dc2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/519a80d7c1d95c6cc0b67f686d15fe27c6910de0", + "reference": "519a80d7c1d95c6cc0b67f686d15fe27c6910de0", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2396,28 +2412,84 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-02-03T14:58:37+00:00" + "time": "2018-03-19T22:32:39+00:00" + }, + { + "name": "symfony/debug", + "version": "v4.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "5961d02d48828671f5d8a7805e06579d692f6ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/5961d02d48828671f5d8a7805e06579d692f6ede", + "reference": "5961d02d48828671f5d8a7805e06579d692f6ede", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "~3.4|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-04-03T05:24:00+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "26726ddc01601dc9393f2afc3369ce1ca64e4537" + "reference": "1a4cffeb059226ff6bee9f48acb388faf674afff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/26726ddc01601dc9393f2afc3369ce1ca64e4537", - "reference": "26726ddc01601dc9393f2afc3369ce1ca64e4537", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1a4cffeb059226ff6bee9f48acb388faf674afff", + "reference": "1a4cffeb059226ff6bee9f48acb388faf674afff", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^5.5.9|>=7.0.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/css-selector": "~3.4|~4.0" + "symfony/css-selector": "~2.8|~3.0|~4.0" }, "suggest": { "symfony/css-selector": "" @@ -2425,7 +2497,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2452,34 +2524,34 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-02-22T10:50:29+00:00" + "time": "2018-03-19T22:32:39+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "85eaf6a8ec915487abac52e133efc4a268204428" + "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/85eaf6a8ec915487abac52e133efc4a268204428", - "reference": "85eaf6a8ec915487abac52e133efc4a268204428", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fdd5abcebd1061ec647089c6c41a07ed60af09f8", + "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^5.5.9|>=7.0.8" }, "conflict": { - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<3.3" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2488,7 +2560,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2515,29 +2587,29 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-02-14T14:11:10+00:00" + "time": "2018-04-06T07:35:25+00:00" }, { "name": "symfony/finder", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "44a796d2ecc2a16a5fc8f2956a34ee617934d55f" + "reference": "bd14efe8b1fabc4de82bf50dce62f05f9a102433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/44a796d2ecc2a16a5fc8f2956a34ee617934d55f", - "reference": "44a796d2ecc2a16a5fc8f2956a34ee617934d55f", + "url": "https://api.github.com/repos/symfony/finder/zipball/bd14efe8b1fabc4de82bf50dce62f05f9a102433", + "reference": "bd14efe8b1fabc4de82bf50dce62f05f9a102433", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2564,7 +2636,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-03-05T18:28:26+00:00" + "time": "2018-04-04T05:07:11+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2625,71 +2697,22 @@ ], "time": "2018-01-30T19:27:44+00:00" }, - { - "name": "symfony/process", - "version": "v4.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "6ed08502a7c9559da8e60ea343bdbd19c3350b3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6ed08502a7c9559da8e60ea343bdbd19c3350b3e", - "reference": "6ed08502a7c9559da8e60ea343bdbd19c3350b3e", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Process Component", - "homepage": "https://symfony.com", - "time": "2018-02-19T12:18:43+00:00" - }, { "name": "symfony/yaml", - "version": "v4.0.6", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "de5f125ea39de846b90b313b2cfb031a0152d223" + "reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/de5f125ea39de846b90b313b2cfb031a0152d223", - "reference": "de5f125ea39de846b90b313b2cfb031a0152d223", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a42f9da85c7c38d59f5e53f076fe81a091f894d0", + "reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^5.5.9|>=7.0.8" }, "conflict": { "symfony/console": "<3.4" @@ -2703,7 +2726,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -2730,7 +2753,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-02-19T20:08:53+00:00" + "time": "2018-04-03T05:14:20+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/TranslatableBehavior.php b/src/TranslatableBehavior.php new file mode 100644 index 0000000..63935d1 --- /dev/null +++ b/src/TranslatableBehavior.php @@ -0,0 +1,239 @@ + + * @since 1.0 + */ +class TranslatableBehavior extends Behavior +{ + /** + * The owner of this behavior. + * + * @var BaseActiveRecord + */ + public $owner; + + /** + * Name of translation relation in main model. + * + * @var string + */ + public $translationRelationName = 'translations'; + + /** + * Name of attribute in translation model that contains language. + * + * @var string + */ + public $translationLanguageAttrName = 'language'; + + /** + * List of attribute names from translation model that should be translated. + * + * @var string[] + */ + public $translationAttributeList; + + /** + * Patter for the attribute name in validation errors list. + * + * @var string + */ + public $attributeNamePattern = '%name% [%language%]'; + + /** + * Temp storage for translation entity objects. + * + * @var array + */ + protected $translationsBuffer = []; + + + /** + * Translate model to needed language. + * + * Alias for @see getTranslation() method. + * + * @param null|string $language + * + * @return \yii\db\ActiveRecord|BaseActiveRecord + */ + final public function translateTo($language = null) + { + return $this->getTranslation($language); + } + + /** + * Returns translation entity object for needed language. + * + * @param null|string $language By default uses application current language. + * + * @return \yii\db\ActiveRecord|BaseActiveRecord + */ + public function getTranslation($language = null) + { + $language = $language ?: Yii::$app->language; + $translations = $this->getModelTranslations(); + + // search translation by language in exists translations + foreach ($translations as $translation) { + // if translation exists - return it + if ($translation->getAttribute($this->translationLanguageAttrName) === $language) { + $this->translationsBuffer[] = $translation; + + return $translation; + } + } + + // if translation doesn't exist - create and return + $translationEntityClass = $this->owner->getRelation($this->translationRelationName)->modelClass; + + /* @var BaseActiveRecord $translation */ + $translation = new $translationEntityClass(); + $translation->setAttribute($this->translationLanguageAttrName, $language); + $translations[] = $translation; + $this->translationsBuffer = $translations; + $this->owner->populateRelation($this->translationRelationName, $translations); + + return $translation; + } + + /** + * Check whether translation exists. + * + * @param null|string $language By default uses application current language. + * + * @return bool + */ + public function hasTranslation($language = null) + { + $language = $language ?: Yii::$app->language; + + foreach ($this->getModelTranslations() as $translation) { + if ($translation->getAttribute($this->translationLanguageAttrName) === $language) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function events() + { + return [ + BaseActiveRecord::EVENT_AFTER_VALIDATE => 'afterValidate', + BaseActiveRecord::EVENT_AFTER_INSERT => 'afterSave', + BaseActiveRecord::EVENT_AFTER_UPDATE => 'afterSave', + ]; + } + + /** + * Triggers after validation of the main model. + */ + public function afterValidate() + { + $translations = $this->getModelTranslations(); + + $isValid = Model::validateMultiple($translations, $this->translationAttributeList); + + if (!$isValid) { + foreach ($translations as $translation) { + foreach ($translation->getErrors() as $attribute => $errors) { + $attribute = strtr($this->attributeNamePattern, [ + '%name%' => $attribute, + '%language%' => $translation->{$this->translationLanguageAttrName}, + ]); + + if (is_array($errors)) { + foreach ($errors as $error) { + $this->owner->addError($attribute, $error); + } + } else { + $this->owner->addError($attribute, $errors); + } + } + } + } + } + + /** + * Triggers after saving of the main model. + */ + public function afterSave() + { + foreach ($this->translationsBuffer as $translation) { + $this->owner->link($this->translationRelationName, $translation); + } + } + + /** + * {@inheritdoc} + */ + public function canGetProperty($name, $checkVars = true) + { + return in_array($name, $this->translationAttributeList) + ?: $this->owner->canGetProperty($name, $checkVars); + } + + /** + * {@inheritdoc} + */ + public function canSetProperty($name, $checkVars = true) + { + return in_array($name, $this->translationAttributeList) + ?: $this->owner->canSetProperty($name, $checkVars); + } + + /** + * {@inheritdoc} + */ + public function __get($name) + { + $translation = $this->getTranslation(); + + if ($translation->hasAttribute($name)) { + return $translation->getAttribute($name); + } + + return $this->owner->$name; + } + + /** + * {@inheritdoc} + */ + public function __set($name, $value) + { + $translation = $this->getTranslation(); + + if ($translation->hasAttribute($name)) { + $translation->setAttribute($name, $value); + } + + $this->owner->$name = $value; + } + + /** + * @return \yii\db\ActiveRecord[] + */ + protected function getModelTranslations() + { + return $this->owner->{$this->translationRelationName}; + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php new file mode 100644 index 0000000..9d4be9a --- /dev/null +++ b/tests/_bootstrap.php @@ -0,0 +1,15 @@ + 'test-app', + 'class' => yii\console\Application::className(), + 'language' => 'en', + + 'basePath' => Yii::getAlias('@tests'), + 'vendorPath' => Yii::getAlias('@vendor'), + 'runtimePath' => Yii::getAlias('@tests/_output'), + + 'bootstrap' => [], + 'components' => [ + 'db' => require __DIR__ . '/db.php', + ], + 'params' => [], +]; diff --git a/tests/_config/db.php b/tests/_config/db.php new file mode 100644 index 0000000..ca32fc8 --- /dev/null +++ b/tests/_config/db.php @@ -0,0 +1,9 @@ + yii\db\Connection::className(), + 'dsn' => 'sqlite:' . Yii::getAlias('@tests/_output/test.db'), + 'username' => '', + 'password' => '', + 'charset' => 'utf8', +]; diff --git a/tests/_data/dump.sql b/tests/_data/dump.sql new file mode 100644 index 0000000..8df21f8 --- /dev/null +++ b/tests/_data/dump.sql @@ -0,0 +1,21 @@ +PRAGMA foreign_keys = on; +BEGIN TRANSACTION; + +-- Table: product +DROP TABLE IF EXISTS product; +CREATE TABLE product( + id INTEGER PRIMARY KEY AUTOINCREMENT +); + +-- Table: product_translation +DROP TABLE IF EXISTS product_translation; +CREATE TABLE product_translation( + id INTEGER PRIMARY KEY AUTOINCREMENT, + product_id INTEGER NOT NULL, + language STING NOT NULL, + title STRING NOT NULL, + description TEXT NOT NULL +); + +COMMIT TRANSACTION; +PRAGMA foreign_keys = on; diff --git a/tests/_output/.gitignore b/tests/_output/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/_output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/_support/.gitignore b/tests/_support/.gitignore new file mode 100644 index 0000000..36e264c --- /dev/null +++ b/tests/_support/.gitignore @@ -0,0 +1 @@ +_generated diff --git a/tests/_support/Helper/Unit.php b/tests/_support/Helper/Unit.php new file mode 100644 index 0000000..4d27aa3 --- /dev/null +++ b/tests/_support/Helper/Unit.php @@ -0,0 +1,10 @@ + + * @since 1.0 + */ +class Product extends ActiveRecord +{ + /** + * {@inheritdoc} + */ + public static function tableName() + { + return 'product'; + } + + /** + * {@inheritdoc} + */ + public function behaviors() + { + return [ + 'translatable' => [ + 'class' => TranslatableBehavior::className(), + 'translationAttributeList' => [ + 'title', + 'description', + ], + ], + ]; + } + + /** + * Relations for translations. + * + * @return \yii\db\ActiveQuery + */ + public function getTranslations() + { + return $this->hasMany(ProductTranslation::className(), ['product_id' => 'id']); + } +} diff --git a/tests/entities/ProductTranslation.php b/tests/entities/ProductTranslation.php new file mode 100644 index 0000000..166d676 --- /dev/null +++ b/tests/entities/ProductTranslation.php @@ -0,0 +1,47 @@ + + * @since 1.0 + */ +class ProductTranslation extends ActiveRecord +{ + /** + * {@inheritdoc} + */ + public static function tableName() + { + return 'product_translation'; + } + + /** + * {@inheritdoc} + */ + public function rules() + { + return [ + ['title', 'required'], + ['title', 'string'], + + ['description', 'required'], + ['description', 'string'], + ]; + } +} diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml new file mode 100644 index 0000000..fbae2b6 --- /dev/null +++ b/tests/unit.suite.yml @@ -0,0 +1,7 @@ +class_name: UnitTester +bootstrap: false +modules: + enabled: + - Db + - Asserts + - Yii2 diff --git a/tests/unit/DbTestCase.php b/tests/unit/DbTestCase.php new file mode 100644 index 0000000..d53fc10 --- /dev/null +++ b/tests/unit/DbTestCase.php @@ -0,0 +1,52 @@ + + * @since 1.0 + */ +class DbTestCase extends TestCase +{ + use FixtureTrait; + + /** + * @var \UnitTester + */ + protected $tester; + + + /** + * {@inheritdoc} + */ + protected function _before() + { + parent::_before(); + $this->loadFixtures(); + } + + /** + * {@inheritdoc} + */ + protected function _after() + { + $this->unloadFixtures(); + parent::_after(); + } + + /** + * {@inheritdoc} + */ + public function fixtures() + { + } +} diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php new file mode 100644 index 0000000..2717d8a --- /dev/null +++ b/tests/unit/TestCase.php @@ -0,0 +1,22 @@ + + * @since 1.0 + */ +class TestCase extends \Codeception\Test\Unit +{ + /** + * @var \UnitTester + */ + protected $tester; +} diff --git a/tests/unit/TranslatableBehaviorTest.php b/tests/unit/TranslatableBehaviorTest.php new file mode 100644 index 0000000..53b66d3 --- /dev/null +++ b/tests/unit/TranslatableBehaviorTest.php @@ -0,0 +1,130 @@ + + * @since 1.0 + */ +class TranslatableBehaviorTest extends TestCase +{ + /** + * @var Product + */ + private $product; + + /** + * {@inheritdoc} + */ + protected function _before() + { + $this->product = new Product(); + } + + public function testCreateModel() + { + // english + $this->product->title = '[en] Test title'; + $this->product->description = '[en] Test description'; + + // french + $this->product->translateTo('fr')->title = '[fr] Test title'; + $this->product->translateTo('fr')->description = '[fr] Test description'; + + $this->product->insert(); + + $this->tester->seeRecord(ProductTranslation::className(), [ + 'language' => 'en', + 'title' => '[en] Test title', + 'description' => '[en] Test description', + ]); + $this->tester->seeRecord(ProductTranslation::className(), [ + 'language' => 'fr', + 'title' => '[fr] Test title', + 'description' => '[fr] Test description' + ]); + } + + public function testUpdateModel() + { + // english + $this->product->title = '[en] New product'; + $this->product->description = '[en] This is new product'; + + // french + $this->product->translateTo('fr')->title = '[fr] New product'; + $this->product->translateTo('fr')->description = '[fr] This is new product'; + + $this->product->insert(); + + $product = Product::findOne($this->product->id); + + // english + $product->getTranslation()->title = '[en] Updated product'; + $product->getTranslation()->description = '[en] This is updated product'; + + // french + $product->translateTo('fr')->title = '[fr] Updated product'; + $product->translateTo('fr')->description = '[fr] This is updated product'; + + $product->update(); + + $this->tester->seeRecord(ProductTranslation::className(), [ + 'language' => 'en', + 'title' => '[en] Updated product', + 'description' => '[en] This is updated product', + ]); + $this->tester->seeRecord(ProductTranslation::className(), [ + 'language' => 'fr', + 'title' => '[fr] Updated product', + 'description' => '[fr] This is updated product', + ]); + } + + public function testValidationErrors() + { + $this->product->title = null; + $this->product->description = null; + $this->product->translateTo('fr')->title = null; + $this->product->translateTo('fr')->description = null; + $this->product->validate(); + + $this->assertTrue($this->product->hasErrors()); + $this->assertSame( + [ + 'title [en]' => ['Title cannot be blank.'], + 'description [en]' => ['Description cannot be blank.'], + + 'title [fr]' => ['Title cannot be blank.'], + 'description [fr]' => ['Description cannot be blank.'], + ], + $this->product->getErrors() + ); + } + + public function testModelHasProperty() + { + $this->assertTrue($this->product->hasProperty('title')); + $this->assertTrue($this->product->hasProperty('description')); + } + + public function testHasTranslation() + { + $this->assertFalse($this->product->hasTranslation()); + $this->assertFalse($this->product->hasTranslation('fr')); + + $this->product->getTranslation('fr'); + $this->assertFalse($this->product->hasTranslation()); + $this->assertTrue($this->product->hasTranslation('fr')); + } +}